/*
 * Copyright (C) 2013 JPEXS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jpexs.decompiler.flash.xfl;

import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.helpers.Highlighting;
import com.jpexs.decompiler.flash.tags.CSMTextSettingsTag;
import com.jpexs.decompiler.flash.tags.DefineButton2Tag;
import com.jpexs.decompiler.flash.tags.DefineButtonCxformTag;
import com.jpexs.decompiler.flash.tags.DefineButtonTag;
import com.jpexs.decompiler.flash.tags.DefineEditTextTag;
import com.jpexs.decompiler.flash.tags.DefineFontNameTag;
import com.jpexs.decompiler.flash.tags.DefineSoundTag;
import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
import com.jpexs.decompiler.flash.tags.DefineText2Tag;
import com.jpexs.decompiler.flash.tags.DefineTextTag;
import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag;
import com.jpexs.decompiler.flash.tags.DoActionTag;
import com.jpexs.decompiler.flash.tags.DoInitActionTag;
import com.jpexs.decompiler.flash.tags.ExportAssetsTag;
import com.jpexs.decompiler.flash.tags.FileAttributesTag;
import com.jpexs.decompiler.flash.tags.FrameLabelTag;
import com.jpexs.decompiler.flash.tags.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag;
import com.jpexs.decompiler.flash.tags.ShowFrameTag;
import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag;
import com.jpexs.decompiler.flash.tags.SoundStreamHeadTypeTag;
import com.jpexs.decompiler.flash.tags.StartSoundTag;
import com.jpexs.decompiler.flash.tags.SymbolClassTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.ButtonTag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.FontTag;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
import com.jpexs.decompiler.flash.tags.base.RemoveTag;
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
import com.jpexs.decompiler.flash.tags.base.TextTag;
import com.jpexs.decompiler.flash.types.BUTTONCONDACTION;
import com.jpexs.decompiler.flash.types.BUTTONRECORD;
import com.jpexs.decompiler.flash.types.CXFORM;
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
import com.jpexs.decompiler.flash.types.FILLSTYLE;
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.GRADIENT;
import com.jpexs.decompiler.flash.types.GRADRECORD;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLE2;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.decompiler.flash.types.SOUNDENVELOPE;
import com.jpexs.decompiler.flash.types.TEXTRECORD;
import com.jpexs.decompiler.flash.types.filters.BEVELFILTER;
import com.jpexs.decompiler.flash.types.filters.BLURFILTER;
import com.jpexs.decompiler.flash.types.filters.COLORMATRIXFILTER;
import com.jpexs.decompiler.flash.types.filters.DROPSHADOWFILTER;
import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.decompiler.flash.types.filters.GLOWFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER;
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.decompiler.flash.types.sound.MP3FRAME;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 *
 * @author JPEXS
 */
public class XFLConverter {

    public static final int KEY_MODE_NORMAL = 9728;
    public static final int KEY_MODE_CLASSIC_TWEEN = 22017;
    public static final int KEY_MODE_SHAPE_TWEEN = 17922;
    public static final int KEY_MODE_MOTION_TWEEN = 8195;
    public static final int KEY_MODE_SHAPE_LAYERS = 8192;

    private XFLConverter() {
    }

    public static String convertShapeEdge(MATRIX mat, SHAPERECORD record, int x, int y) {
        String ret = "";
        if (record instanceof StyleChangeRecord) {
            StyleChangeRecord scr = (StyleChangeRecord) record;
            Point p = new Point(scr.moveDeltaX, scr.moveDeltaY);
            //p = mat.apply(p);
            if (scr.stateMoveTo) {
                return "! " + p.x + " " + p.y;
            }
            return "";
        }
        if (record instanceof StraightEdgeRecord) {
            StraightEdgeRecord ser = (StraightEdgeRecord) record;
            if (ser.generalLineFlag || ser.vertLineFlag) {
                y += ser.deltaY;
            }
            if (ser.generalLineFlag || (!ser.vertLineFlag)) {
                x += ser.deltaX;
            }
            Point p = new Point(x, y);
            //p = mat.apply(p);
            return "| " + p.x + " " + p.y;
        }
        if (record instanceof CurvedEdgeRecord) {
            CurvedEdgeRecord cer = (CurvedEdgeRecord) record;
            int controlX = cer.controlDeltaX + x;
            int controlY = cer.controlDeltaY + y;
            int anchorX = cer.anchorDeltaX + controlX;
            int anchorY = cer.anchorDeltaY + controlY;
            Point control = new Point(controlX, controlY);
            //control = mat.apply(control);
            Point anchor = new Point(anchorX, anchorY);
            //anchor = mat.apply(anchor);
            return "[ " + control.x + " " + control.y + " " + anchor.x + " " + anchor.y;
        }
        return ret;
    }

    public static String convertShapeEdges(int startX, int startY, MATRIX mat, List<SHAPERECORD> records) {
        String ret = "";
        int x = startX;
        int y = startY;
        ret += "!" + startX + " " + startY;
        for (SHAPERECORD rec : records) {
            ret += convertShapeEdge(mat, rec, x, y);
            x = rec.changeX(x);
            y = rec.changeY(y);
        }
        return ret;
    }

    public static String convertLineStyle(LINESTYLE ls, int shapeNum) {
        return "<SolidStroke weight=\"" + (((float) ls.width) / 20.0) + "\">"
                + "<fill>"
                + "<SolidColor color=\"" + (shapeNum == 3 ? ls.colorA.toHexRGB() : ls.color.toHexRGB()) + "\""
                + (shapeNum == 3 ? " alpha=\"" + ls.colorA.getAlphaFloat() + "\"" : "")
                + " />"
                + "</fill>"
                + "</SolidStroke>";
    }

    public static String convertLineStyle(HashMap<Integer, CharacterTag> characters, LINESTYLE2 ls, int shapeNum) {
        String ret = "";
        String params = "";
        if (ls.pixelHintingFlag) {
            params += " pixelHinting=\"true\"";
        }
        if (ls.width == 1) {
            params += " solidStyle=\"hairline\"";
        }
        if ((!ls.noHScaleFlag) && (!ls.noVScaleFlag)) {
            params += " scaleMode=\"normal\"";
        } else if ((!ls.noHScaleFlag) && ls.noVScaleFlag) {
            params += " scaleMode=\"horizontal\"";
        } else if (ls.noHScaleFlag && (!ls.noVScaleFlag)) {
            params += " scaleMode=\"vertical\"";
        }

        switch (ls.endCapStyle) {  //What about endCapStyle?
            case LINESTYLE2.NO_CAP:
                params += " caps=\"none\"";
                break;
            case LINESTYLE2.SQUARE_CAP:
                params += " caps=\"square\"";
                break;
        }
        switch (ls.joinStyle) {
            case LINESTYLE2.BEVEL_JOIN:
                params += " joints=\"bevel\"";
                break;
            case LINESTYLE2.MITER_JOIN:
                params += " joints=\"miter\"";
                float miterLimitFactor = toFloat(ls.miterLimitFactor);
                if (miterLimitFactor != 3.0f) {
                    params += " miterLimit=\"" + miterLimitFactor + "\"";
                }
                break;
        }

        ret += "<SolidStroke weight=\"" + (((float) ls.width) / 20.0) + "\"";
        ret += params;
        ret += ">";
        ret += "<fill>";

        if (!ls.hasFillFlag) {
            ret += "<SolidColor color=\"" + ls.color.toHexRGB() + "\""
                    + (ls.color.getAlphaFloat() != 1 ? " alpha=\"" + ls.color.getAlphaFloat() + "\"" : "")
                    + " />";
        } else {
            ret += convertFillStyle(null/* FIXME */, characters, ls.fillType, shapeNum);
        }
        ret += "</fill>";
        ret += "</SolidStroke>";
        return ret;
    }

    private static float toFloat(int i) {
        return ((float) i) / (1 << 16);
    }

    public static String convertFillStyle(MATRIX mat, HashMap<Integer, CharacterTag> characters, FILLSTYLE fs, int shapeNum) {
        if (mat == null) {
            mat = new MATRIX();
        }
        String ret = "";
        //ret += "<FillStyle index=\"" + index + "\">";
        switch (fs.fillStyleType) {
            case FILLSTYLE.SOLID:
                ret += "<SolidColor color=\"";
                if (shapeNum >= 3) {
                    ret += fs.colorA.toHexRGB();
                } else {
                    ret += fs.color.toHexRGB();
                }
                ret += "\"";
                if (shapeNum >= 3) {
                    ret += " alpha=\"" + fs.colorA.getAlphaFloat() + "\"";
                }
                ret += " />";
                break;
            case FILLSTYLE.REPEATING_BITMAP:
            case FILLSTYLE.CLIPPED_BITMAP:
            case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP:
            case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP:
                ret += "<BitmapFill";
                ret += " bitmapPath=\"";
                CharacterTag bitmapCh = characters.get(fs.bitmapId);
                if (bitmapCh instanceof ImageTag) {
                    ImageTag it = (ImageTag) bitmapCh;
                    ret += "bitmap" + bitmapCh.getCharacterID() + "." + it.getImageFormat();
                }
                ret += "\"";

                if ((fs.fillStyleType == FILLSTYLE.CLIPPED_BITMAP) || (fs.fillStyleType == FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP)) {
                    ret += " bitmapIsClipped=\"true\"";
                }

                ret += ">";
                ret += "<matrix>" + convertMatrix(fs.bitmapMatrix) + "</matrix>";
                ret += "</BitmapFill>";
                break;
            case FILLSTYLE.LINEAR_GRADIENT:
            case FILLSTYLE.RADIAL_GRADIENT:
            case FILLSTYLE.FOCAL_RADIAL_GRADIENT:

                if (fs.fillStyleType == FILLSTYLE.LINEAR_GRADIENT) {
                    ret += "<LinearGradient";
                } else {
                    ret += "<RadialGradient";
                    ret += " focalPointRatio=\"";
                    if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                        ret += fs.focalGradient.focalPoint;
                    } else {
                        ret += "0";
                    }
                    ret += "\"";
                }

                int interpolationMode;
                if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                    interpolationMode = fs.focalGradient.interPolationMode;
                } else {
                    interpolationMode = fs.gradient.interPolationMode;
                }
                int spreadMode;
                if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                    spreadMode = fs.focalGradient.spreadMode;
                } else {
                    spreadMode = fs.gradient.spreadMode;
                }
                if (interpolationMode == GRADIENT.INTERPOLATION_LINEAR_RGB_MODE) {
                    ret += " interpolationMethod=\"linearRGB\"";
                }
                switch (spreadMode) {
                    case GRADIENT.SPREAD_PAD_MODE:

                        break;
                    case GRADIENT.SPREAD_REFLECT_MODE:
                        ret += " spreadMethod=\"reflect\"";
                        break;
                    case GRADIENT.SPREAD_REPEAT_MODE:
                        ret += " spreadMethod=\"repeat\"";
                        break;
                }

                ret += ">";

                ret += "<matrix>" + convertMatrix(fs.gradientMatrix) + "</matrix>";
                GRADRECORD records[];
                if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                    records = fs.focalGradient.gradientRecords;
                } else {
                    records = fs.gradient.gradientRecords;
                }
                for (GRADRECORD rec : records) {
                    ret += "<GradientEntry";
                    ret += " color=\"" + (shapeNum >= 3 ? rec.colorA.toHexRGB() : rec.color.toHexRGB()) + "\"";
                    if (shapeNum >= 3) {
                        ret += " alpha=\"" + rec.colorA.getAlphaFloat() + "\"";
                    }
                    ret += " ratio=\"" + rec.getRatioFloat() + "\"";
                    ret += " />";
                }
                if (fs.fillStyleType == FILLSTYLE.LINEAR_GRADIENT) {
                    ret += "</LinearGradient>";
                } else {
                    ret += "</RadialGradient>";
                }
                break;
        }
        //ret += "</FillStyle>";
        return ret;

    }

    public static String convertMatrix(MATRIX m) {
        String ret = "";
        if (m == null) {
            m = new MATRIX();
        }
        ret += "<Matrix ";
        ret += "tx=\"" + (((float) m.translateX) / 20.0) + "\" ";
        ret += "ty=\"" + (((float) m.translateY) / 20.0) + "\" ";
        if (m.hasScale) {
            ret += "a=\"" + m.toFloat(m.scaleX) + "\" ";
            ret += "d=\"" + m.toFloat(m.scaleY) + "\" ";
        }
        if (m.hasRotate) {
            ret += "b=\"" + m.toFloat(m.rotateSkew0) + "\" ";
            ret += "c=\"" + m.toFloat(m.rotateSkew1) + "\" ";
        }
        ret += "/>";
        return ret;
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, SHAPE shape) {
        return convertShape(characters, mat, shapeNum, shape.shapeRecords);
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, SHAPEWITHSTYLE shape) {
        return convertShape(characters, mat, shapeNum, shape.shapeRecords, shape.fillStyles, shape.lineStyles, false);
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, ShapeTag shape) {
        return convertShape(characters, mat, shape.getShapeNum(), shape.getShapes());
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords) {
        return convertShape(characters, mat, shapeNum, shapeRecords, null, null, false);
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape) {
        String ret = "";
        if (mat == null) {
            mat = new MATRIX();
        }
        List<SHAPERECORD> edges = new ArrayList<>();
        //int fillStyleCount = 0;
        int lineStyleCount = 0;
        int fillStyle0 = -1;
        int fillStyle1 = -1;
        int strokeStyle = -1;
        //String edgesStr = "";
        String fillsStr = "";
        String strokesStr = "";
        fillsStr += "<fills>";
        strokesStr += "<strokes>";
        //edgesStr += "<edges>";
        List<String> layers = new ArrayList<>();
        String currentLayer = "";

        int fillStyleCount = 0;
        //List<String> fillStylesStr = new ArrayList<>();
        if (fillStyles != null) {
            for (FILLSTYLE fs : fillStyles.fillStyles) {
                fillsStr += "<FillStyle index=\"" + (fillStyleCount + 1) + "\">";
                fillsStr += convertFillStyle(mat, characters, fs, shapeNum);
                fillsStr += "</FillStyle>";
                fillStyleCount++;
            }
        }
        if (lineStyles != null) {
            if (lineStyles.lineStyles2 != null) { //(shapeNum == 4) {
                for (int l = 0; l < lineStyles.lineStyles2.length; l++) {
                    strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                    strokesStr += convertLineStyle(characters, lineStyles.lineStyles2[l], shapeNum);
                    strokesStr += "</StrokeStyle>";
                    lineStyleCount++;
                }
            } else {
                for (int l = 0; l < lineStyles.lineStyles.length; l++) {
                    strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                    strokesStr += convertLineStyle(lineStyles.lineStyles[l], shapeNum);
                    strokesStr += "</StrokeStyle>";
                    lineStyleCount++;
                }
            }
        }

        fillsStr += "</fills>";
        strokesStr += "</strokes>";

        int layer = 1;

        if ((fillStyleCount > 0) || (lineStyleCount > 0)) {
            currentLayer += "<DOMLayer name=\"Layer " + (layer++) + "\">"; //color=\"#4FFF4F\"
            currentLayer += "<frames>";
            currentLayer += "<DOMFrame index=\"0\" motionTweenScale=\"false\" keyMode=\"" + KEY_MODE_SHAPE_LAYERS + "\">";
            currentLayer += "<elements>";
            currentLayer += "<DOMShape isFloating=\"true\">";
            currentLayer += fillsStr;
            currentLayer += strokesStr;
            currentLayer += "<edges>";
        }
        //edgesStr += "</edges>";

        /*for (SHAPERECORD edge : shapeRecords) {
         if (edge instanceof StyleChangeRecord) {
         StyleChangeRecord scr = (StyleChangeRecord) edge;
         if (scr.stateNewStyles) {
         for (int f = 0; f < scr.fillStyles.fillStyles.length; f++) {
         fillStylesStr.add(convertFillStyle(mat, characters, scr.fillStyles.fillStyles[f], shapeNum));
         }
         }
         }
         }
         boolean usedFillStyles[] = new boolean[fillStylesStr.size()];
         int fillStyleCount = 0;
         lastFillStyleCount = 0;
         if (fillStyles != null) {
         lastFillStyleCount = fillStyles.fillStyles.length;
         fillStyleCount += lastFillStyleCount;
         }
         for (SHAPERECORD edge : shapeRecords) {
         if (edge instanceof StyleChangeRecord) {
         StyleChangeRecord scr = (StyleChangeRecord) edge;
         if (scr.stateNewStyles) {
         lastFillStyleCount = scr.fillStyles.fillStyles.length;
         fillStyleCount += lastFillStyleCount;
         }
         if (scr.stateFillStyle0 && scr.fillStyle0 > 0) {
         usedFillStyles[fillStyleCount - lastFillStyleCount + scr.fillStyle0 - 1] = true;
         }
         if (scr.stateFillStyle1 && scr.fillStyle1 > 0) {
         usedFillStyles[fillStyleCount - lastFillStyleCount + scr.fillStyle1 - 1] = true;
         }
         }
         }
         */
        /*int mapPos = 0;
         HashMap<Integer, Integer> fillStylesMap = new HashMap<>();
         for (int f = 0; f < fillStyleCount; f++) {
         if (usedFillStyles[f]) {
         fillStylesMap.put(f, mapPos);
         mapPos++;
         fillsStr += "<FillStyle index=\"" + mapPos + "\">";
         fillsStr += fillStylesStr.get(f);
         fillsStr += "</FillStyle>";
         }
         }*/

        int x = 0;
        int y = 0;
        int startEdgeX = 0;
        int startEdgeY = 0;


        LINESTYLEARRAY actualLinestyles = lineStyles;
        int strokeStyleOrig = 0;
        fillStyleCount = fillStyles.fillStyles.length;
        for (SHAPERECORD edge : shapeRecords) {
            if (edge instanceof StyleChangeRecord) {
                StyleChangeRecord scr = (StyleChangeRecord) edge;
                boolean styleChange = false;
                int lastFillStyle1 = fillStyle1;
                int lastFillStyle0 = fillStyle0;
                int lastStrokeStyle = strokeStyle;
                if (scr.stateNewStyles) {
                    fillsStr = "<fills>";
                    strokesStr = "<strokes>";
                    if (fillStyleCount > 0) {

                        if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {

                            boolean empty = false;
                            if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
                                if (shapeNum == 4) {
                                    if (strokeStyleOrig > 0) {
                                        if (!actualLinestyles.lineStyles2[strokeStyleOrig].hasFillFlag) {
                                            if (actualLinestyles.lineStyles2[strokeStyleOrig].color.alpha == 0) {
                                                if (actualLinestyles.lineStyles2[strokeStyleOrig].color.red == 0) {
                                                    if (actualLinestyles.lineStyles2[strokeStyleOrig].color.green == 0) {
                                                        if (actualLinestyles.lineStyles2[strokeStyleOrig].color.blue == 0) {
                                                            empty = true;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            if (!empty) {
                                currentLayer += "<Edge";
                                if (fillStyle0 > -1) {
                                    currentLayer += " fillStyle0=\"" + fillStyle0 + "\"";
                                }
                                if (fillStyle1 > -1) {
                                    currentLayer += " fillStyle1=\"" + fillStyle1 + "\"";
                                }
                                if (strokeStyle > -1) {
                                    currentLayer += " strokeStyle=\"" + strokeStyle + "\"";
                                }
                                currentLayer += " edges=\"" + convertShapeEdges(startEdgeX, startEdgeY, mat, edges) + "\" />";
                            }
                        }

                        currentLayer += "</edges>";
                        currentLayer += "</DOMShape>";
                        currentLayer += "</elements>";
                        currentLayer += "</DOMFrame>";
                        currentLayer += "</frames>";
                        currentLayer += "</DOMLayer>";
                        layers.add(currentLayer);
                        currentLayer = "";
                    }

                    currentLayer += "<DOMLayer name=\"Layer " + (layer++) + "\">"; //color=\"#4FFF4F\"
                    currentLayer += "<frames>";
                    currentLayer += "<DOMFrame index=\"0\" motionTweenScale=\"false\" keyMode=\"" + KEY_MODE_SHAPE_LAYERS + "\">";
                    currentLayer += "<elements>";
                    currentLayer += "<DOMShape isFloating=\"true\">";
                    //ret += convertShape(characters, null, shape);
                    for (int f = 0; f < scr.fillStyles.fillStyles.length; f++) {
                        fillsStr += "<FillStyle index=\"" + (f + 1) + "\">";
                        fillsStr += convertFillStyle(mat, characters, scr.fillStyles.fillStyles[f], shapeNum);
                        fillsStr += "</FillStyle>";
                        fillStyleCount++;
                    }

                    if (shapeNum == 4) {
                        for (int l = 0; l < scr.lineStyles.lineStyles2.length; l++) {
                            strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                            strokesStr += convertLineStyle(characters, scr.lineStyles.lineStyles2[l], shapeNum);
                            strokesStr += "</StrokeStyle>";
                            lineStyleCount++;
                        }
                    } else {
                        for (int l = 0; l < scr.lineStyles.lineStyles.length; l++) {
                            strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                            strokesStr += convertLineStyle(scr.lineStyles.lineStyles[l], shapeNum);
                            strokesStr += "</StrokeStyle>";
                            lineStyleCount++;
                        }
                    }
                    fillsStr += "</fills>";
                    strokesStr += "</strokes>";
                    currentLayer += fillsStr;
                    currentLayer += strokesStr;
                    currentLayer += "<edges>";
                    //lastFillStyleCount = scr.fillStyles.fillStyles.length;
                    //fillStyleCount += lastFillStyleCount;
                    //lastLineStyleCount = (shapeNum == 4) ? scr.lineStyles.lineStyles2.length : scr.lineStyles.lineStyles.length;
                    actualLinestyles = scr.lineStyles;
                }
                if (scr.stateFillStyle0) {
                    /*edgeStyle += " fillStyle0=\"";
                     edgeStyle += fillStyleCount - lastFillStyleCount + scr.fillStyle0;
                     edgeStyle += "\"";*/
                    int fillStyle0_new = scr.fillStyle0;// == 0 ? 0 : fillStylesMap.get(fillStyleCount - lastFillStyleCount + scr.fillStyle0 - 1) + 1;
                    //fillStyle0 = fillStyle0_new;
                    if (morphshape) { //???
                        fillStyle1 = fillStyle0_new;
                    } else {
                        fillStyle0 = fillStyle0_new;
                    }
                    styleChange = true;
                }
                if (scr.stateFillStyle1) {
                    /*edgeStyle += " fillStyle1=\"";
                     edgeStyle += fillStyleCount - lastFillStyleCount + scr.fillStyle1;
                     edgeStyle += "\"";*/
                    int fillStyle1_new = scr.fillStyle1;// == 0 ? 0 : fillStylesMap.get(fillStyleCount - lastFillStyleCount + scr.fillStyle1 - 1) + 1;
                    if (morphshape) {
                        fillStyle0 = fillStyle1_new;
                    } else {
                        fillStyle1 = fillStyle1_new;
                    }
                    //fillStyle1 = fillStyle1_new;
                    styleChange = true;
                }
                if (scr.stateLineStyle) {
                    /*edgeStyle += " strokeStyle=\"";
                     edgeStyle += lineStyleCount - lastLineStyleCount + scr.lineStyle;
                     edgeStyle += "\"";*/
                    strokeStyle = scr.lineStyle;// == 0 ? 0 : lineStyleCount - lastLineStyleCount + scr.lineStyle;
                    strokeStyleOrig = scr.lineStyle - 1;
                    styleChange = true;
                }
                if (!edges.isEmpty()) {
                    if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
                        //if(true){
                        boolean empty = false;
                        if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
                            if (shapeNum == 4) {
                                if (strokeStyleOrig > 0) {
                                    if (!actualLinestyles.lineStyles2[strokeStyleOrig].hasFillFlag) {
                                        if (actualLinestyles.lineStyles2[strokeStyleOrig].color.alpha == 0) {
                                            if (actualLinestyles.lineStyles2[strokeStyleOrig].color.red == 0) {
                                                if (actualLinestyles.lineStyles2[strokeStyleOrig].color.green == 0) {
                                                    if (actualLinestyles.lineStyles2[strokeStyleOrig].color.blue == 0) {
                                                        empty = true;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if (!empty) {
                            currentLayer += "<Edge";
                            if (fillStyle0 > -1) {
                                currentLayer += " fillStyle0=\"" + lastFillStyle0 + "\"";
                            }
                            if (fillStyle1 > -1) {
                                currentLayer += " fillStyle1=\"" + lastFillStyle1 + "\"";
                            }
                            if (strokeStyle > -1) {
                                currentLayer += " strokeStyle=\"" + lastStrokeStyle + "\"";
                            }
                            currentLayer += " edges=\"" + convertShapeEdges(startEdgeX, startEdgeY, mat, edges) + "\" />";
                        }

                        startEdgeX = x;
                        startEdgeY = y;
                        //strokeStyle = -1;
                        //fillStyle0 = -1;
                        //fillStyle1 = -1;
                    }
                    edges.clear();
                }
            }
            edges.add(edge);
            x = edge.changeX(x);
            y = edge.changeY(y);
        }
        if (!edges.isEmpty()) {
            if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {

                boolean empty = false;
                if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
                    if (shapeNum == 4) {
                        if (strokeStyleOrig > 0) {
                            if (!actualLinestyles.lineStyles2[strokeStyleOrig].hasFillFlag) {
                                if (actualLinestyles.lineStyles2[strokeStyleOrig].color.alpha == 0) {
                                    if (actualLinestyles.lineStyles2[strokeStyleOrig].color.red == 0) {
                                        if (actualLinestyles.lineStyles2[strokeStyleOrig].color.green == 0) {
                                            if (actualLinestyles.lineStyles2[strokeStyleOrig].color.blue == 0) {
                                                empty = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if (!empty) {
                    currentLayer += "<Edge";
                    if (fillStyle0 > -1) {
                        currentLayer += " fillStyle0=\"" + fillStyle0 + "\"";
                    }
                    if (fillStyle1 > -1) {
                        currentLayer += " fillStyle1=\"" + fillStyle1 + "\"";
                    }
                    if (strokeStyle > -1) {
                        currentLayer += " strokeStyle=\"" + strokeStyle + "\"";
                    }
                    currentLayer += " edges=\"" + convertShapeEdges(startEdgeX, startEdgeY, mat, edges) + "\" />";
                }
            }
        }
        edges.clear();
        fillsStr += "</fills>";
        strokesStr += "</strokes>";
        currentLayer += "</edges>";
        currentLayer += "</DOMShape>";
        currentLayer += "</elements>";
        currentLayer += "</DOMFrame>";
        currentLayer += "</frames>";
        currentLayer += "</DOMLayer>";
        layers.add(currentLayer);
        for (int l = layers.size() - 1; l >= 0; l--) {
            ret += layers.get(l);
        }
        return ret;
    }

    private static int getLayerCount(List<Tag> tags) {
        int maxDepth = 0;
        for (Tag t : tags) {
            if (t instanceof PlaceObjectTypeTag) {
                int d = ((PlaceObjectTypeTag) t).getDepth();
                if (d > maxDepth) {
                    maxDepth = d;
                }
                int cd = ((PlaceObjectTypeTag) t).getClipDepth();
                if (cd > maxDepth) {
                    maxDepth = cd;
                }
            }
        }
        return maxDepth;
    }

    private static List<Integer> getOneInstanceShapes(List<Tag> tags, HashMap<Integer, CharacterTag> characters) {
        if (true) { //the feature was removed due to multiple layers in some shapes            
            return new ArrayList<>();
        }
        HashMap<Integer, Integer> usages = new HashMap<>();
        for (Tag t : tags) {
            if (t instanceof PlaceObjectTypeTag) {
                PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                int ch = po.getCharacterId();
                if (ch > -1) {
                    if (!usages.containsKey(ch)) {
                        usages.put(ch, 0);
                    }
                    int usageCount = usages.get(ch);
                    if (po.getInstanceName() != null) {
                        usageCount++;
                    }
                    if (po.getColorTransform() != null) {
                        usageCount++;
                    }
                    if (po.getColorTransformWithAlpha() != null) {
                        usageCount++;
                    }
                    if (po.cacheAsBitmap()) {
                        usageCount++;
                    }
                    MATRIX mat = po.getMatrix();
                    if (mat != null) {
                        if (!mat.isEmpty()) {
                            usageCount++;
                        }
                    }
                    usages.put(ch, usageCount + 1);
                }
            }
        }
        List<Integer> ret = new ArrayList<>();
        for (int ch : usages.keySet()) {
            if (usages.get(ch) < 2) {
                ret.add(ch);
            }
        }
        return ret;
    }

    private static HashMap<Integer, CharacterTag> getCharacters(List<Tag> tags) {
        HashMap<Integer, CharacterTag> ret = new HashMap<>();
        int maxId = 0;
        for (Tag t : tags) {
            if (t instanceof CharacterTag) {
                CharacterTag ct = (CharacterTag) t;
                if (ct.getCharacterID() > maxId) {
                    maxId = ct.getCharacterID();
                }
            }
        }
        for (Tag t : tags) {
            if (t instanceof SoundStreamHeadTypeTag) {
                SoundStreamHeadTypeTag ssh = (SoundStreamHeadTypeTag) t;
                ssh.setVirtualCharacterId(++maxId);
            }
            if (t instanceof CharacterTag) {
                CharacterTag ct = (CharacterTag) t;
                ret.put(ct.getCharacterID(), ct);
            }
        }
        return ret;
    }
    private static final String[] BLENDMODES = {
        null,
        null,
        "layer",
        "multiply",
        "screen",
        "lighten",
        "darken",
        "difference",
        "add",
        "subtract",
        "invert",
        "alpha",
        "erase",
        "overlay",
        "hardligh"
    };

    private static double radToDeg(double rad) {
        return rad * 180 / Math.PI;
    }

    private static String doubleToString(double d) {
        String ds = "" + d;
        if (ds.endsWith(".0")) {
            ds = ds.substring(0, ds.length() - 2);
        }
        return ds;
    }

    public static String convertFilter(FILTER filter) {
        String ret = "";
        if (filter instanceof DROPSHADOWFILTER) {
            DROPSHADOWFILTER dsf = (DROPSHADOWFILTER) filter;
            ret += "<DropShadowFilter";
            if (dsf.dropShadowColor.alpha != 255) {
                ret += " alpha=\"" + doubleToString(dsf.dropShadowColor.getAlphaFloat()) + "\"";
            }
            ret += " angle=\"" + doubleToString(radToDeg(dsf.angle)) + "\"";
            ret += " blurX=\"" + doubleToString(dsf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(dsf.blurY) + "\"";
            ret += " color=\"" + dsf.dropShadowColor.toHexRGB() + "\"";
            ret += " distance=\"" + doubleToString(dsf.distance) + "\"";
            if (!dsf.compositeSource) {
                ret += " hideObject=\"true\"";
            }
            if (dsf.innerShadow) {
                ret += " inner=\"true\"";
            }
            if (dsf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " quality=\"" + dsf.passes + "\"";
            ret += " strength=\"" + doubleToString(dsf.strength) + "\"";
            ret += " />";
        } else if (filter instanceof BLURFILTER) {
            BLURFILTER bf = (BLURFILTER) filter;
            ret += "<BlurFilter";
            ret += " blurX=\"" + doubleToString(bf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(bf.blurY) + "\"";
            ret += " quality=\"" + bf.passes + "\"";
            ret += " />";
        } else if (filter instanceof GLOWFILTER) {
            GLOWFILTER gf = (GLOWFILTER) filter;
            ret += "<GlowFilter";
            if (gf.glowColor.alpha != 255) {
                ret += " alpha=\"" + gf.glowColor.getAlphaFloat() + "\"";
            }
            ret += " blurX=\"" + doubleToString(gf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(gf.blurY) + "\"";
            ret += " color=\"" + gf.glowColor.toHexRGB() + "\"";

            if (gf.innerGlow) {
                ret += " inner=\"true\"";
            }
            if (gf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " quality=\"" + gf.passes + "\"";
            ret += " strength=\"" + doubleToString(gf.strength) + "\"";
            ret += " />";
        } else if (filter instanceof BEVELFILTER) {
            BEVELFILTER bf = (BEVELFILTER) filter;
            ret += "<BevelFilter";
            ret += " blurX=\"" + doubleToString(bf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(bf.blurY) + "\"";
            ret += " quality=\"" + bf.passes + "\"";
            ret += " angle=\"" + doubleToString(radToDeg(bf.angle)) + "\"";
            ret += " distance=\"" + bf.distance + "\"";
            if (bf.highlightColor.alpha != 255) {
                ret += " highlightAlpha=\"" + bf.highlightColor.getAlphaFloat() + "\"";
            }
            ret += " highlightColor=\"" + bf.highlightColor.toHexRGB() + "\"";
            if (bf.knockout) {
                ret += " knockout=\"true\"";
            }
            if (bf.shadowColor.alpha != 255) {
                ret += " shadowAlpha=\"" + bf.shadowColor.getAlphaFloat() + "\"";
            }
            ret += " shadowColor=\"" + bf.shadowColor.toHexRGB() + "\"";
            ret += " strength=\"" + doubleToString(bf.strength) + "\"";
            if (bf.onTop && !bf.innerShadow) {
                ret += " type=\"full\"";
            } else if (!bf.innerShadow) {
                ret += " type=\"outer\"";
            }
            ret += " />";
        } else if (filter instanceof GRADIENTGLOWFILTER) {
            GRADIENTGLOWFILTER ggf = (GRADIENTGLOWFILTER) filter;
            ret += "<GradientGlowFilter";
            ret += " angle=\"" + doubleToString(radToDeg(ggf.angle)) + "\"";

            ret += " blurX=\"" + doubleToString(ggf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(ggf.blurY) + "\"";
            ret += " quality=\"" + ggf.passes + "\"";
            ret += " distance=\"" + doubleToString(ggf.distance) + "\"";
            if (ggf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " strength=\"" + doubleToString(ggf.strength) + "\"";
            if (ggf.onTop && !ggf.innerShadow) {
                ret += " type=\"full\"";
            } else if (!ggf.innerShadow) {
                ret += " type=\"outer\"";
            }
            ret += ">";
            for (int g = 0; g < ggf.gradientColors.length; g++) {
                RGBA gc = ggf.gradientColors[g];
                ret += "<GradientEntry color=\"" + gc.toHexRGB() + "\"";
                if (gc.alpha != 255) {
                    ret += " alpha=\"" + gc.getAlphaFloat() + "\"";
                }
                ret += " ratio=\"" + doubleToString(((float) ggf.gradientRatio[g]) / 255.0) + "\"";
                ret += "/>";
            }
            ret += "</GradientGlowFilter>";
        } else if (filter instanceof GRADIENTBEVELFILTER) {
            GRADIENTBEVELFILTER gbf = (GRADIENTBEVELFILTER) filter;
            ret += "<GradientBevelFilter";
            ret += " angle=\"" + doubleToString(radToDeg(gbf.angle)) + "\"";

            ret += " blurX=\"" + doubleToString(gbf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(gbf.blurY) + "\"";
            ret += " quality=\"" + gbf.passes + "\"";
            ret += " distance=\"" + doubleToString(gbf.distance) + "\"";
            if (gbf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " strength=\"" + doubleToString(gbf.strength) + "\"";
            if (gbf.onTop && !gbf.innerShadow) {
                ret += " type=\"full\"";
            } else if (!gbf.innerShadow) {
                ret += " type=\"outer\"";
            }
            ret += ">";
            for (int g = 0; g < gbf.gradientColors.length; g++) {
                RGBA gc = gbf.gradientColors[g];
                ret += "<GradientEntry color=\"" + gc.toHexRGB() + "\"";
                if (gc.alpha != 255) {
                    ret += " alpha=\"" + gc.getAlphaFloat() + "\"";
                }
                ret += " ratio=\"" + doubleToString(((float) gbf.gradientRatio[g]) / 255.0) + "\"";
                ret += "/>";
            }
            ret += "</GradientBevelFilter>";
        } else if (filter instanceof COLORMATRIXFILTER) {
            COLORMATRIXFILTER cmf = (COLORMATRIXFILTER) filter;
            ret += convertAdjustColorFilter(cmf);
        }
        return ret;
    }

    public static String convertSymbolInstance(String name, MATRIX matrix, CXFORM colorTransform, CXFORMWITHALPHA colorTransformAlpha, boolean cacheAsBitmap, int blendMode, List<FILTER> filters, boolean isVisible, RGBA backgroundColor, CharacterTag tag, HashMap<Integer, CharacterTag> characters, List<Tag> tags) {
        String ret = "";
        if (matrix == null) {
            matrix = new MATRIX();
        }
        if (tag instanceof DefineButtonTag) {
            DefineButtonTag bt = (DefineButtonTag) tag;
            for (Tag t : tags) {
                if (t instanceof DefineButtonCxformTag) {
                    DefineButtonCxformTag bcx = (DefineButtonCxformTag) t;
                    if (bcx.buttonId == bt.buttonId) {
                        colorTransform = bcx.buttonColorTransform;
                    }
                }
            }
        }

        ret += "<DOMSymbolInstance libraryItemName=\"" + "Symbol " + tag.getCharacterID() + "\"";
        if (name != null) {
            ret += " name=\"" + name + "\"";
        }
        String blendModeStr = null;
        if (blendMode < BLENDMODES.length) {
            blendModeStr = BLENDMODES[blendMode];
        }
        if (blendModeStr != null) {
            ret += " blendMode=\"" + blendModeStr + "\"";
        }
        if (tag instanceof ShapeTag) {
            ret += " symbolType=\"graphic\" loop=\"loop\"";
        } else if (tag instanceof DefineSpriteTag) {
            DefineSpriteTag sprite = (DefineSpriteTag) tag;
            RECT spriteRect = sprite.getRect(characters);
            double centerPoint3DX = twipToPixel(matrix.translateX + spriteRect.getWidth() / 2);
            double centerPoint3DY = twipToPixel(matrix.translateY + spriteRect.getHeight() / 2);
            ret += " centerPoint3DX=\"" + centerPoint3DX + "\" centerPoint3DY=\"" + centerPoint3DY + "\"";
        } else if (tag instanceof ButtonTag) {
            ret += " symbolType=\"button\"";
        }
        if (cacheAsBitmap) {
            ret += " cacheAsBitmap=\"true\"";
        }
        if (!isVisible) {
            ret += " isVisible=\"false\"";
        }
        ret += ">";
        ret += "<matrix>";
        ret += convertMatrix(matrix);
        ret += "</matrix>";
        ret += "<transformationPoint><Point/></transformationPoint>";

        if (backgroundColor != null) {
            ret += "<MatteColor color=\"" + backgroundColor.toHexRGB() + "\"";
            if (backgroundColor.alpha != 255) {
                ret += " alpha=\"" + doubleToString(backgroundColor.getAlphaFloat()) + "\"";
            }
            ret += "/>";
        }
        if (colorTransform != null) {
            ret += "<color><Color";
            if (colorTransform.hasMultTerms) {
                ret += " redMultiplier=\"" + (((float) colorTransform.redMultTerm) / 256.0f) + "\"";
                ret += " greenMultiplier=\"" + (((float) colorTransform.greenMultTerm) / 256.0f) + "\"";
                ret += " blueMultiplier=\"" + (((float) colorTransform.blueMultTerm) / 256.0f) + "\"";
            }
            if (colorTransform.hasAddTerms) {
                ret += " redOffset=\"" + colorTransform.redAddTerm + "\"";
                ret += " greenOffset=\"" + colorTransform.greenAddTerm + "\"";
                ret += " blueOffset=\"" + colorTransform.blueAddTerm + "\"";
            }
            ret += "/></color>";
        } else if (colorTransformAlpha != null) {
            ret += "<color><Color";
            if (colorTransformAlpha.hasMultTerms) {
                ret += " alphaMultiplier=\"" + (((float) colorTransformAlpha.alphaMultTerm) / 256.0f) + "\"";
                ret += " redMultiplier=\"" + (((float) colorTransformAlpha.redMultTerm) / 256.0f) + "\"";
                ret += " greenMultiplier=\"" + (((float) colorTransformAlpha.greenMultTerm) / 256.0f) + "\"";
                ret += " blueMultiplier=\"" + (((float) colorTransformAlpha.blueMultTerm) / 256.0f) + "\"";
            }
            if (colorTransformAlpha.hasAddTerms) {
                ret += " alphaOffset=\"" + colorTransformAlpha.alphaAddTerm + "\"";
                ret += " redOffset=\"" + colorTransformAlpha.redAddTerm + "\"";
                ret += " greenOffset=\"" + colorTransformAlpha.greenAddTerm + "\"";
                ret += " blueOffset=\"" + colorTransformAlpha.blueAddTerm + "\"";
            }
            ret += "/></color>";
        }
        if (filters != null) {
            ret += "<filters>";
            for (FILTER f : filters) {
                ret += convertFilter(f);
            }
            ret += "</filters>";
        }
        if (tag instanceof DefineButtonTag) {
            ret += "<Actionscript><script><![CDATA[";
            ret += "on(press){\r\n";
            ret += convertActionScript(((DefineButtonTag) tag));
            ret += "}";
            ret += "]]></script></Actionscript>";
        }
        if (tag instanceof DefineButton2Tag) {
            DefineButton2Tag db2 = (DefineButton2Tag) tag;
            if (!db2.actions.isEmpty()) {
                ret += "<Actionscript><script><![CDATA[";
                for (BUTTONCONDACTION bca : db2.actions) {
                    List<String> events = new ArrayList<>();
                    if (bca.condOverUpToOverDown) {
                        events.add("press");
                    }
                    if (bca.condOverDownToOverUp) {
                        events.add("release");
                    }
                    if (bca.condOutDownToIdle) {
                        events.add("releaseOutside");
                    }
                    if (bca.condIdleToOverUp) {
                        events.add("rollOver");
                    }
                    if (bca.condOverUpToIddle) {
                        events.add("rollOut");
                    }
                    if (bca.condOverDownToOutDown) {
                        events.add("dragOut");
                    }
                    if (bca.condOutDownToOverDown) {
                        events.add("dragOver");
                    }
                    if (bca.condKeyPress > 0) {
                        String keyNames[] = {
                            null,
                            "<Left>",
                            "<Right>",
                            "<Home>",
                            "<End>",
                            "<Insert>",
                            "<Delete>",
                            null,
                            "<Backspace>",
                            null,
                            null,
                            null,
                            null,
                            "<Enter>",
                            "<Up>",
                            "<Down>",
                            "<PageUp>",
                            "<PageDown>",
                            "<Tab>",
                            "<Escape>",
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            "<Space>"
                        };
                        if ((bca.condKeyPress < keyNames.length) && (bca.condKeyPress > 0) && (keyNames[bca.condKeyPress] != null)) {
                            events.add("keyPress \"" + keyNames[bca.condKeyPress] + "\"");
                        } else {
                            events.add("keyPress \"" + (char) bca.condKeyPress + "\"");
                        }
                    }
                    String onStr = "";
                    for (int i = 0; i < events.size(); i++) {
                        if (i > 0) {
                            onStr += ", ";
                        }
                        onStr += events.get(i);
                    }
                    ret += "on(" + onStr + "){\r\n";
                    ret += convertActionScript(bca);
                    ret += "}";
                }
                ret += "]]></script></Actionscript>";
            }
        }
        ret += "</DOMSymbolInstance>";
        return ret;
    }

    private static String convertActionScript(ASMSource as) {
        String decompiledASHilighted = com.jpexs.decompiler.flash.action.Action.actionsToSource(as.getActions(SWF.DEFAULT_VERSION), SWF.DEFAULT_VERSION);
        return Highlighting.stripHilights(decompiledASHilighted);
    }

    private static long getTimestamp() {
        return new Date().getTime() / 1000;
    }

    public static String convertLibrary(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, List<Integer> oneInstanceShapes, String backgroundColor, List<Tag> tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files) {

        //TODO: Imported assets
        //linkageImportForRS="true" linkageIdentifier="xxx" linkageURL="yyy.swf"
        String ret = "";
        List<String> media = new ArrayList<>();
        List<String> symbols = new ArrayList<>();
        for (int ch : characters.keySet()) {
            CharacterTag symbol = characters.get(ch);
            if ((symbol instanceof ShapeTag) && oneInstanceShapes.contains(symbol.getCharacterID())) {
                continue; //shapes with 1 ocurrence are not added to library
            }
            if ((symbol instanceof ShapeTag) || (symbol instanceof DefineSpriteTag) || (symbol instanceof ButtonTag)) {
                String symbolStr = "";

                symbolStr += "<DOMSymbolItem xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://ns.adobe.com/xfl/2008/\" name=\"Symbol " + symbol.getCharacterID() + "\" lastModified=\"" + getTimestamp() + "\""; //TODO:itemID
                if (symbol instanceof ShapeTag) {
                    symbolStr += " symbolType=\"graphic\"";
                } else if (symbol instanceof ButtonTag) {
                    symbolStr += " symbolType=\"button\"";
                    if (((ButtonTag) symbol).trackAsMenu()) {
                        symbolStr += "  trackAsMenu=\"true\"";
                    }
                }
                boolean linkageExportForAS = false;
                if (characterClasses.containsKey(symbol.getCharacterID())) {
                    linkageExportForAS = true;
                    symbolStr += " linkageClassName=\"" + characterClasses.get(symbol.getCharacterID()) + "\"";
                }
                if (characterVariables.containsKey(symbol.getCharacterID())) {
                    linkageExportForAS = true;
                    symbolStr += " linkageIdentifier=\"" + characterVariables.get(symbol.getCharacterID()) + "\"";
                }
                if (linkageExportForAS) {
                    symbolStr += " linkageExportForAS=\"true\"";
                }
                symbolStr += ">";
                symbolStr += "<timeline>";
                String itemIcon = null;
                if (symbol instanceof ButtonTag) {
                    itemIcon = "0";
                    symbolStr += "<DOMTimeline name=\"Symbol " + symbol.getCharacterID() + "\" currentFrame=\"0\">";
                    symbolStr += "<layers>";

                    ButtonTag button = (ButtonTag) symbol;
                    List<BUTTONRECORD> records = button.getRecords();
                    String frames[] = {"", "", "", ""};

                    int maxDepth = 0;
                    for (BUTTONRECORD rec : records) {
                        if (rec.placeDepth > maxDepth) {
                            maxDepth = rec.placeDepth;
                        }
                    }
                    for (int i = maxDepth; i >= 1; i--) {
                        symbolStr += "<DOMLayer name=\"Layer " + (maxDepth - i + 1) + "\"";
                        if (i == 1) {
                            symbolStr += " current=\"true\" isSelected=\"true\"";
                        }
                        symbolStr += ">"; //color=\"#4FFF4F\"
                        symbolStr += "<frames>";
                        int lastFrame = 0;
                        loopframes:
                        for (int frame = 1; frame <= 4; frame++) {
                            for (BUTTONRECORD rec : records) {
                                if (rec.placeDepth == i) {
                                    boolean ok = false;
                                    switch (frame) {
                                        case 1:
                                            ok = rec.buttonStateUp;
                                            break;
                                        case 2:
                                            ok = rec.buttonStateOver;
                                            break;
                                        case 3:
                                            ok = rec.buttonStateDown;
                                            break;
                                        case 4:
                                            ok = rec.buttonStateHitTest;
                                            break;
                                    }
                                    if (!ok) {
                                        continue;
                                    }
                                    CXFORMWITHALPHA colorTransformAlpha = null;
                                    int blendMode = 0;
                                    List<FILTER> filters = new ArrayList<>();
                                    if (button instanceof DefineButton2Tag) {
                                        colorTransformAlpha = rec.colorTransform;
                                        if (rec.buttonHasBlendMode) {
                                            blendMode = rec.blendMode;
                                        }
                                        if (rec.buttonHasFilterList) {
                                            filters = rec.filterList;
                                        }
                                    }
                                    CharacterTag character = characters.get(rec.characterId);
                                    MATRIX matrix = rec.placeMatrix;
                                    String recCharStr = "";
                                    if (character instanceof TextTag) {
                                        recCharStr = convertText(tags, (TextTag) character, matrix, filters);
                                    } else if (character instanceof DefineVideoStreamTag) {
                                        recCharStr = convertVideoInstance(matrix, (DefineVideoStreamTag) character);
                                    } else {
                                        recCharStr = convertSymbolInstance(null, matrix, null, colorTransformAlpha, false, blendMode, filters, true, null, characters.get(rec.characterId), characters, tags);
                                    }
                                    int duration = frame - lastFrame;
                                    lastFrame = frame;
                                    if (duration > 0) {
                                        if (duration > 1) {
                                            symbolStr += "<DOMFrame index=\"";
                                            symbolStr += (frame - duration);
                                            symbolStr += "\"";
                                            symbolStr += " duration=\"" + (duration - 1) + "\"";
                                            symbolStr += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                                            symbolStr += "<elements>";
                                            symbolStr += "</elements>";
                                            symbolStr += "</DOMFrame>";
                                        }
                                        symbolStr += "<DOMFrame index=\"";
                                        symbolStr += (frame - 1);
                                        symbolStr += "\"";
                                        symbolStr += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                                        symbolStr += "<elements>";
                                        symbolStr += recCharStr;
                                        symbolStr += "</elements>";
                                        symbolStr += "</DOMFrame>";
                                    }
                                }
                            }
                        }
                        symbolStr += "</frames>";
                        symbolStr += "</DOMLayer>";
                    }
                    symbolStr += "</layers>";
                    symbolStr += "</DOMTimeline>";
                } else if (symbol instanceof DefineSpriteTag) {
                    DefineSpriteTag sprite = (DefineSpriteTag) symbol;
                    String initActionScript = "";

                    symbolStr += convertTimeline(sprite.spriteId, oneInstanceShapes, backgroundColor, tags, sprite.getSubTags(), characters, "Symbol " + symbol.getCharacterID());
                } else if (symbol instanceof ShapeTag) {
                    itemIcon = "1";
                    ShapeTag shape = (ShapeTag) symbol;
                    symbolStr += "<DOMTimeline name=\"Symbol " + symbol.getCharacterID() + "\" currentFrame=\"0\">";
                    symbolStr += "<layers>";
                    symbolStr += convertShape(characters, null, shape);
                    symbolStr += "</layers>";
                    symbolStr += "</DOMTimeline>";
                }
                symbolStr += "</timeline>";
                symbolStr += "</DOMSymbolItem>";
                symbolStr = prettyFormatXML(symbolStr);
                String symbolFile = "Symbol " + symbol.getCharacterID() + ".xml";
                try {
                    files.put(symbolFile, symbolStr.getBytes("UTF-8"));
                } catch (UnsupportedEncodingException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                String symbLinkStr = "";
                symbLinkStr += "<Include href=\"" + symbolFile + "\"";
                if (itemIcon != null) {
                    symbLinkStr += " itemIcon=\"" + itemIcon + "\"";
                }
                symbLinkStr += " loadImmediate=\"false\" lastModified=\"" + getTimestamp() + "\"/>"; //TODO: itemID=\"518de416-00000341\"
                symbols.add(symbLinkStr);
            } else if (symbol instanceof ImageTag) {
                ImageTag imageTag = (ImageTag) symbol;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                BufferedImage image = imageTag.getImage(tags);
                String format = imageTag.getImageFormat();
                try {
                    ImageIO.write(image, format.toUpperCase(), baos);
                } catch (IOException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                String symbolFile = "bitmap" + symbol.getCharacterID() + "." + imageTag.getImageFormat();
                files.put(symbolFile, baos.toByteArray());
                String mediaLinkStr = "<DOMBitmapItem name=\"" + symbolFile + "\" sourceLastImported=\"" + getTimestamp() + "\" externalFileSize=\"" + baos.toByteArray().length + "\"";
                if (format.equals("png") || format.equals("gif")) {
                    mediaLinkStr += " useImportedJPEGData=\"false\" compressionType=\"lossless\" originalCompressionType=\"lossless\"";
                } else if (format.equals("jpg")) {
                    mediaLinkStr += " isJPEG=\"true\"";
                }
                if (characterClasses.containsKey(symbol.getCharacterID())) {
                    mediaLinkStr += " linkageExportForAS=\"true\" linkageClassName=\"" + characterClasses.get(symbol.getCharacterID()) + "\"";
                }
                mediaLinkStr += " quality=\"50\" href=\"" + symbolFile + "\" bitmapDataHRef=\"M " + (media.size() + 1) + " " + getTimestamp() + ".dat\" frameRight=\"" + image.getWidth() + "\" frameBottom=\"" + image.getHeight() + "\"/>\n";
                media.add(mediaLinkStr);

            } else if ((symbol instanceof SoundStreamHeadTypeTag) || (symbol instanceof DefineSoundTag)) {
                int soundFormat = 0;
                int soundRate = 0;
                int soundType = 0;
                int soundSize = 0;
                long soundSampleCount = 0;
                byte soundData[] = new byte[0];
                int rateMap[] = {5, 11, 22, 44};
                String exportFormat = "flv";
                if (symbol instanceof SoundStreamHeadTypeTag) {
                    SoundStreamHeadTypeTag sstream = (SoundStreamHeadTypeTag) symbol;
                    soundFormat = sstream.getSoundFormat();
                    soundRate = sstream.getSoundRate();
                    soundType = sstream.getSoundType();
                    soundSize = sstream.getSoundSize();
                    soundSampleCount = sstream.getSoundSampleCount();
                    boolean found = false;
                    for (Tag t : tags) {
                        if (found && (t instanceof SoundStreamBlockTag)) {
                            SoundStreamBlockTag bl = (SoundStreamBlockTag) t;
                            soundData = bl.getData(SWF.DEFAULT_VERSION);
                            break;
                        }
                        if (t == symbol) {
                            found = true;
                        }
                    }
                } else if (symbol instanceof DefineSoundTag) {
                    DefineSoundTag sound = (DefineSoundTag) symbol;
                    soundFormat = sound.soundFormat;
                    soundRate = sound.soundRate;
                    soundType = sound.soundType;
                    soundData = sound.soundData;
                    soundSize = sound.soundSize;
                    soundSampleCount = sound.soundSampleCount;
                }
                int format = 0;
                int bits = 0;
                if ((soundFormat == DefineSoundTag.FORMAT_ADPCM)
                        || (soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN)
                        || (soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN)) {
                    if (soundType == 1) { //stereo
                        format += 1;
                    }
                    switch (soundRate) {
                        case 0:
                            format += 2;
                            break;
                        case 1:
                            format += 6;
                            break;
                        case 2:
                            format += 10;
                            break;
                        case 3:
                            format += 14;
                            break;
                    }
                }
                if (soundFormat == DefineSoundTag.FORMAT_SPEEX) {
                    bits = 18;
                }
                if (soundFormat == DefineSoundTag.FORMAT_ADPCM) {
                    SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(soundData), SWF.DEFAULT_VERSION);
                    exportFormat = "wav";
                    try {
                        int adpcmCodeSize = (int) sis.readUB(2);
                        bits = 2 + adpcmCodeSize;
                    } catch (IOException ex) {
                        Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                if (soundFormat == DefineSoundTag.FORMAT_MP3) {
                    exportFormat = "mp3";
                    if (soundType == 0) { //mono
                        format += 1;
                    }
                    format += 4; //quality best
                    SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(soundData), SWF.DEFAULT_VERSION);
                    try {
                        sis.readSI16();
                        MP3FRAME frame = new MP3FRAME(sis);
                        int bitRate = frame.getBitrate();

                        switch (bitRate) {
                            case 8:
                                bits = 6;
                                break;
                            case 16:
                                bits = 7;
                                break;
                            case 20:
                                bits = 8;
                                break;
                            case 24:
                                bits = 9;
                                break;
                            case 32:
                                bits = 10;
                                break;
                            case 48:
                                bits = 11;
                                break;
                            case 56:
                                bits = 12;
                                break;
                            case 64:
                                bits = 13;
                                break;
                            case 80:
                                bits = 14;
                                break;
                            case 112:
                                bits = 15;
                                break;
                            case 128:
                                bits = 16;
                                break;
                            case 160:
                                bits = 17;
                                break;

                        }
                    } catch (IOException ex) {
                        Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                byte data[] = new byte[0];
                try {
                    data = swf.exportSound(symbol);
                } catch (IOException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                if (!exportFormat.equals("flv")) { //FLV import format unsupported
                    String symbolFile = "sound" + symbol.getCharacterID() + "." + exportFormat;
                    files.put(symbolFile, data);
                    String mediaLinkStr = "<DOMSoundItem name=\"" + symbolFile + "\" sourceLastImported=\"" + getTimestamp() + "\" externalFileSize=\"" + data.length + "\"";
                    mediaLinkStr += " href=\"" + symbolFile + "\"";
                    mediaLinkStr += " format=\"";
                    mediaLinkStr += rateMap[soundRate] + "kHz";
                    mediaLinkStr += " " + (soundSize == 1 ? "16bit" : "8bit");
                    mediaLinkStr += " " + (soundType == 1 ? "Stereo" : "Mono");
                    mediaLinkStr += "\"";
                    mediaLinkStr += " exportFormat=\"" + format + "\" exportBits=\"" + bits + "\" sampleCount=\"" + soundSampleCount + "\"";

                    if (characterClasses.containsKey(symbol.getCharacterID())) {
                        mediaLinkStr += " linkageExportForAS=\"true\" linkageClassName=\"" + characterClasses.get(symbol.getCharacterID()) + "\"";
                    }

                    mediaLinkStr += "/>\n";
                    media.add(mediaLinkStr);
                }
            } else if (symbol instanceof DefineVideoStreamTag) {
                DefineVideoStreamTag video = (DefineVideoStreamTag) symbol;
                String videoType = "no media";
                switch (video.codecID) {
                    case 2:
                        videoType = "h263 media";
                        break;
                    case 3:
                        videoType = "screen share media";
                        break;
                    case 4:
                        videoType = "vp6 media";
                        break;
                    case 5:
                        videoType = "vp6 alpha media";
                        break;
                }

                byte data[] = new byte[0];
                try {
                    data = swf.exportMovie(video);
                } catch (IOException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                String symbolFile = "movie" + symbol.getCharacterID() + "." + "flv";
                files.put(symbolFile, data);
                String mediaLinkStr = "<DOMVideoItem name=\"" + symbolFile + "\" sourceLastImported=\"" + getTimestamp() + "\" externalFileSize=\"" + data.length + "\"";
                mediaLinkStr += " href=\"" + symbolFile + "\"";
                mediaLinkStr += " videoType=\"" + videoType + "\"";
                mediaLinkStr += " fps=\"" + swf.frameRate + "\"";
                mediaLinkStr += " width=\"" + video.width + "\"";
                mediaLinkStr += " height=\"" + video.height + "\"";
                double len = ((double) video.numFrames) / ((double) swf.frameRate);
                mediaLinkStr += " length=\"" + len + "\"";
                if (characterClasses.containsKey(symbol.getCharacterID())) {
                    mediaLinkStr += " linkageExportForAS=\"true\" linkageClassName=\"" + characterClasses.get(symbol.getCharacterID()) + "\"";
                }
                mediaLinkStr += "/>\n";
                media.add(mediaLinkStr);
            }

        }
        if (!media.isEmpty()) {
            ret += "<media>";
            for (String m : media) {
                ret += m;
            }
            ret += "</media>";
        }
        if (!symbols.isEmpty()) {
            ret += "<symbols>";
            for (String s : symbols) {
                ret += s;
            }
            ret += "</symbols>";
        }
        return ret;
    }

    private static String prettyFormatXML(String input) {
        int indent = 5;
        try {
            Source xmlInput = new StreamSource(new StringReader(input));
            StringWriter stringWriter = new StringWriter();
            StreamResult xmlOutput = new StreamResult(stringWriter);
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            transformerFactory.setAttribute("indent-number", indent);
            Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.transform(xmlInput, xmlOutput);
            return xmlOutput.getWriter().toString();
        } catch (Exception e) {
            System.err.println(input);
            Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, "Pretty print error", e);
            return input;
        }
    }

    private static String convertFrame(boolean shapeTween, HashMap<Integer, CharacterTag> characters, List<Tag> tags, SoundStreamHeadTypeTag soundStreamHead, StartSoundTag startSound, int frame, String frameName, boolean isAnchor, int duration, String actionScript, String elements) {
        String ret = "";
        DefineSoundTag sound = null;
        if (startSound != null) {
            for (Tag t : tags) {
                if (t instanceof DefineSoundTag) {
                    DefineSoundTag s = (DefineSoundTag) t;
                    if (s.soundId == startSound.soundId) {
                        sound = s;
                        break;
                    }
                }
            }
        }

        ret += "<DOMFrame index=\"" + (frame) + "\"";
        if (duration > 1) {
            ret += " duration=\"" + duration + "\"";
        }
        if (frameName != null) {
            ret += " name=\"" + frameName + "\"";
            if (isAnchor) {
                ret += " labelType=\"anchor\" bookmark=\"true\"";
            } else {
                ret += " labelType=\"name\"";
            }
            isAnchor = false;
            frameName = null;
        }
        if (shapeTween) {
            ret += " tweenType=\"shape\" keyMode=\"" + KEY_MODE_SHAPE_TWEEN + "\"";
        } else {
            ret += " keyMode=\"" + KEY_MODE_NORMAL + "\"";
        }
        String soundEnvelopeStr = "";
        if (soundStreamHead != null) {
            ret += " soundName=\"sound" + soundStreamHead.getCharacterID() + "." + soundStreamHead.getExportFormat() + "\"";
            ret += " soundSync=\"stream\"";
            soundEnvelopeStr += "<SoundEnvelope>";
            soundEnvelopeStr += "<SoundEnvelopePoint level0=\"32768\" level1=\"32768\"/>";
            soundEnvelopeStr += "</SoundEnvelope>";
        }
        if (startSound != null && sound != null) {
            ret += " soundName=\"sound" + sound.soundId + "." + sound.getExportFormat() + "\"";
            if (startSound.soundInfo.hasInPoint) {
                ret += " inPoint44=\"" + startSound.soundInfo.inPoint + "\"";
            }
            if (startSound.soundInfo.hasOutPoint) {
                ret += " outPoint44=\"" + startSound.soundInfo.outPoint + "\"";
            }
            if (startSound.soundInfo.hasLoops) {
                if (startSound.soundInfo.loopCount == 32767) {
                    ret += " soundLoopMode=\"loop\"";
                }
                ret += " soundLoop=\"" + startSound.soundInfo.loopCount + "\"";
            }

            if (startSound.soundInfo.syncStop) {
                ret += " soundSync=\"stop\"";
            } else if (startSound.soundInfo.syncNoMultiple) {
                ret += " soundSync=\"start\"";
            };
            soundEnvelopeStr += "<SoundEnvelope>";
            if (startSound.soundInfo.hasEnvelope) {
                for (SOUNDENVELOPE env : startSound.soundInfo.envelopeRecords) {
                    soundEnvelopeStr += "<SoundEnvelopePoint mark44=\"" + env.pos44 + "\" level0=\"" + env.leftLevel + "\" level1=\"" + env.rightLevel + "\"/>";
                }

                if (startSound.soundInfo.envelopeRecords.length == 1
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 32768
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 0) {
                    ret += " soundEffect=\"left channel\"";
                } else if (startSound.soundInfo.envelopeRecords.length == 1
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 0
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 32768) {
                    ret += " soundEffect=\"right channel\"";
                } else if (startSound.soundInfo.envelopeRecords.length == 2
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 32768
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 0
                        && startSound.soundInfo.envelopeRecords[1].leftLevel == 0
                        && startSound.soundInfo.envelopeRecords[1].pos44 == sound.soundSampleCount
                        && startSound.soundInfo.envelopeRecords[1].rightLevel == 32768) {
                    ret += " soundEffect=\"fade left to right\"";
                } else if (startSound.soundInfo.envelopeRecords.length == 2
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 0
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 32768
                        && startSound.soundInfo.envelopeRecords[1].leftLevel == 32768
                        && startSound.soundInfo.envelopeRecords[1].pos44 == sound.soundSampleCount
                        && startSound.soundInfo.envelopeRecords[1].rightLevel == 0) {
                    ret += " soundEffect=\"fade right to left\"";
                } else {
                    ret += " soundEffect=\"custom\"";
                }
                //TODO: fade in, fade out

            } else {
                soundEnvelopeStr += "<SoundEnvelopePoint level0=\"32768\" level1=\"32768\"/>";
            }
            soundEnvelopeStr += "</SoundEnvelope>";
        }
        ret += ">";

        ret += soundEnvelopeStr;
        if (!actionScript.equals("")) {
            ret += "<Actionscript><script><![CDATA[";
            ret += actionScript;
            ret += "]]></script></Actionscript>";
        }
        ret += "<elements>";
        ret += elements;
        ret += "</elements>";
        ret += "</DOMFrame>";
        return ret;
    }

    private static String convertVideoInstance(MATRIX matrix, DefineVideoStreamTag video) {
        String ret = "<DOMVideoInstance libraryItemName=\"movie" + video.characterID + ".flv\" frameRight=\"" + (20 * video.width) + "\" frameBottom=\"" + (20 * video.height) + "\">";
        ret += "<matrix>";
        ret += convertMatrix(matrix);
        ret += "</matrix>";
        ret += "<transformationPoint>";
        ret += "<Point />";
        ret += "</transformationPoint>";
        ret += "</DOMVideoInstance>";
        return ret;
    }

    private static String convertFrames(String prevStr, String afterStr, List<Integer> oneInstanceShapes, List<Tag> tags, List<Tag> timelineTags, HashMap<Integer, CharacterTag> characters, int depth) {
        String ret = "";
        prevStr += "<frames>";
        int frame = -1;
        String elements = "";
        String lastElements = "";

        int duration = 1;
        String frameName = null;
        boolean isAnchor = false;

        CharacterTag character = null;
        MATRIX matrix = null;
        String instanceName = null;
        CXFORM colorTransForm = null;
        CXFORMWITHALPHA colorTransFormAlpha = null;
        boolean cacheAsBitmap = false;
        int blendMode = 0;
        List<FILTER> filters = new ArrayList<>();
        boolean isVisible = true;
        RGBA backGroundColor = null;
        int characterId = -1;
        int ratio = -1;
        boolean shapeTween = false;
        boolean lastShapeTween = false;

        for (Tag t : timelineTags) {
            if (t instanceof PlaceObjectTypeTag) {
                PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                if (po.getDepth() == depth) {
                    int newCharId = po.getCharacterId();
                    if (newCharId == -1) {
                        newCharId = characterId;
                    }
                    characterId = newCharId;
                    if (characters.containsKey(characterId)) {
                        character = characters.get(characterId);
                        if (po.flagMove()) {
                            MATRIX matrix2 = po.getMatrix();
                            if (matrix2 != null) {
                                matrix = matrix2;
                            }
                            String instanceName2 = po.getInstanceName();
                            if (instanceName2 != null) {
                                instanceName = instanceName2;
                            }
                            CXFORM colorTransForm2 = po.getColorTransform();
                            if (colorTransForm2 != null) {
                                colorTransForm = colorTransForm2;
                            }

                            CXFORMWITHALPHA colorTransFormAlpha2 = po.getColorTransformWithAlpha();
                            if (colorTransFormAlpha2 != null) {
                                colorTransFormAlpha = colorTransFormAlpha2;
                            }
                            if (po.cacheAsBitmap()) {
                                cacheAsBitmap = true;
                            }
                            int blendMode2 = po.getBlendMode();
                            if (blendMode2 > 0) {
                                blendMode = blendMode2;
                            }
                            List<FILTER> filters2 = po.getFilters();
                            if (filters2 != null) {
                                filters = filters2;
                            }
                            int ratio2 = po.getRatio();
                            if (ratio2 > -1) {
                                ratio = ratio2;
                            }
                        } else {
                            matrix = po.getMatrix();
                            instanceName = po.getInstanceName();
                            colorTransForm = po.getColorTransform();
                            colorTransFormAlpha = po.getColorTransformWithAlpha();
                            cacheAsBitmap = po.cacheAsBitmap();
                            blendMode = po.getBlendMode();
                            filters = po.getFilters();
                            ratio = po.getRatio();
                        }



                    }
                }
            }

            if (t instanceof RemoveTag) {
                RemoveTag rt = (RemoveTag) t;
                if (rt.getDepth() == depth) {
                    character = null;
                    matrix = null;
                    instanceName = null;
                    colorTransForm = null;
                    colorTransFormAlpha = null;
                    cacheAsBitmap = false;
                    blendMode = 0;
                    filters = new ArrayList<>();
                    isVisible = true;
                    backGroundColor = null;
                    characterId = -1;
                }
            }
            if (t instanceof FrameLabelTag) {
                FrameLabelTag flt = (FrameLabelTag) t;
                frameName = flt.getLabelName();
                isAnchor = flt.isNamedAnchor();
            }

            if (t instanceof ShowFrameTag) {
                elements = "";

                if ((character instanceof ShapeTag) && oneInstanceShapes.contains(characterId)) {
                    elements += convertShape(characters, matrix, (ShapeTag) character);
                    shapeTween = false;
                } else if (character != null) {
                    if (character instanceof MorphShapeTag) {
                        MorphShapeTag m = (MorphShapeTag) character;
                        if (ratio == 65535) {
                            elements += convertShape(characters, matrix, 3, m.getEndEdges().shapeRecords, m.getFillStyles().getEndFillStyles(), m.getLineStyles().getEndLineStyles(m.getShapeNum()), true);
                            shapeTween = false;
                        } else {
                            elements += convertShape(characters, matrix, 3, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true);
                            shapeTween = true;
                        }
                    } else {
                        shapeTween = false;
                        if (character instanceof TextTag) {
                            elements += convertText(tags, (TextTag) character, matrix, filters);
                        } else if (character instanceof DefineVideoStreamTag) {
                            elements += convertVideoInstance(matrix, (DefineVideoStreamTag) character);
                        } else {
                            elements += convertSymbolInstance(instanceName, matrix, colorTransForm, colorTransFormAlpha, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, character, characters, tags);
                        }
                    }
                }

                frame++;
                if (!elements.equals(lastElements) && frame > 0) {
                    ret += convertFrame(lastShapeTween, characters, tags, null, null, frame - duration, frameName, isAnchor, duration, "", lastElements);
                    duration = 1;
                } else if (frame == 0) {
                    duration = 1;
                } else {
                    duration++;
                }

                lastShapeTween = shapeTween;
                lastElements = elements;


                frameName = null;
                isAnchor = false;
            }
        }
        if (!lastElements.equals("")) {
            ret += convertFrame(lastShapeTween, characters, tags, null, null, (frame < 0 ? 0 : frame) - duration, frameName, isAnchor, duration, "", lastElements);
        }
        afterStr = "</frames>" + afterStr;
        if (!ret.equals("")) {
            ret = prevStr + ret + afterStr;
        }
        return ret;
    }

    public static String convertActionScriptLayer(int layerIndex, int spriteId, List<Tag> tags, List<Tag> timeLineTags, String backgroundColor) {
        String ret = "";

        String script = "";
        int duration = 0;
        int frame = 0;
        for (Tag t : tags) {
            if (t instanceof DoInitActionTag) {
                DoInitActionTag dia = (DoInitActionTag) t;
                if (dia.spriteId == spriteId) {
                    script += convertActionScript(dia);
                }
            }
        }
        if (!script.equals("")) {
            script = "#initclip\r\n" + script + "#endinitclip\r\n";
        }
        for (Tag t : timeLineTags) {
            if (t instanceof DoActionTag) {
                DoActionTag da = (DoActionTag) t;
                script += convertActionScript(da);
            }
            if (t instanceof ShowFrameTag) {

                if (script.equals("")) {
                    duration++;
                } else {
                    if (duration > 0) {
                        ret += "<DOMFrame index=\"" + (frame - duration) + "\"";
                        if (duration > 1) {
                            ret += " duration=\"" + duration + "\"";
                        }
                        ret += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                        ret += "<elements>";
                        ret += "</elements>";
                        ret += "</DOMFrame>";
                    }
                    ret += "<DOMFrame index=\"" + frame + "\"";
                    ret += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                    ret += "<Actionscript><script><![CDATA[";
                    ret += script;
                    ret += "]]></script></Actionscript>";
                    ret += "<elements>";
                    ret += "</elements>";
                    ret += "</DOMFrame>";
                    script = "";
                    duration = 0;
                }
                frame++;
            }
        }
        if (!ret.equals("")) {
            ret = "<DOMLayer name=\"Layer " + (layerIndex + 1) + "\" color=\"" + backgroundColor + "\">"
                    + "<frames>"
                    + ret
                    + "</frames>"
                    + "</DOMLayer>";
        }
        return ret;
    }

    public static String convertSoundLayer(int layerIndex, String backgroundColor, HashMap<Integer, CharacterTag> characters, List<Tag> tags, List<Tag> timeLineTags) {
        String ret = "";
        StartSoundTag lastStartSound = null;
        SoundStreamHeadTypeTag lastSoundStreamHead = null;
        StartSoundTag startSound = null;
        SoundStreamHeadTypeTag soundStreamHead = null;
        int duration = 1;
        int frame = 0;
        for (Tag t : timeLineTags) {
            if (t instanceof StartSoundTag) {
                startSound = (StartSoundTag) t;
            }
            if (t instanceof SoundStreamHeadTypeTag) {
                soundStreamHead = (SoundStreamHeadTypeTag) t;
            }
            if (t instanceof ShowFrameTag) {
                if (soundStreamHead != null || startSound != null) {
                    if (lastSoundStreamHead != null || lastStartSound != null) {
                        ret += convertFrame(false, characters, tags, lastSoundStreamHead, lastStartSound, frame, null, false, duration, "", "");
                    }
                    frame += duration;
                    duration = 1;
                    lastSoundStreamHead = soundStreamHead;
                    lastStartSound = startSound;
                    soundStreamHead = null;
                    startSound = null;
                } else {
                    duration++;
                }
            }
        }
        if (lastSoundStreamHead != null || lastStartSound != null) {
            if (frame < 0) {
                frame = 0;
                duration = 1;
            }
            ret += convertFrame(false, characters, tags, lastSoundStreamHead, lastStartSound, frame, null, false, duration, "", "");
        }
        if (!ret.equals("")) {
            ret = "<DOMLayer name=\"Layer " + layerIndex + "\" color=\"" + backgroundColor + "\">"
                    + "<frames>" + ret + "</frames>"
                    + "</DOMLayer>";
        }
        return ret;
    }

    public static String convertTimeline(int spriteId, List<Integer> oneInstanceShapes, String backgroundColor, List<Tag> tags, List<Tag> timelineTags, HashMap<Integer, CharacterTag> characters, String name) {
        String ret = "";
        ret += "<DOMTimeline name=\"" + name + "\">";
        ret += "<layers>";
        int layerCount = getLayerCount(timelineTags);
        Stack<Integer> parentLayers = new Stack<>();
        int index = 0;
        for (int d = layerCount; d >= 1; d--, index++) {
            for (Tag t : timelineTags) {
                if (t instanceof PlaceObjectTypeTag) {
                    PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                    if (po.getClipDepth() == d) {
                        for (int m = po.getDepth(); m < po.getClipDepth(); m++) {
                            parentLayers.push(index);
                        }

                        ret += "<DOMLayer name=\"Layer " + (index + 1) + "\" color=\"" + backgroundColor + "\" ";
                        ret += " layerType=\"mask\" locked=\"true\"";
                        ret += ">";
                        ret += convertFrames("", "", oneInstanceShapes, tags, timelineTags, characters, po.getDepth());
                        ret += "</DOMLayer>";
                        index++;
                        break;
                    }
                }
            }


            boolean hasClipDepth = false;
            for (Tag t : timelineTags) {
                if (t instanceof PlaceObjectTypeTag) {
                    PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                    if (po.getDepth() == d) {
                        if (po.getClipDepth() != -1) {
                            hasClipDepth = true;
                            break;
                        }
                    }
                }
            }
            if (hasClipDepth) {
                index--;
                continue;
            }
            int parentLayer = -1;
            if (!parentLayers.isEmpty()) {
                parentLayer = parentLayers.pop();
            }
            String layerPrev = "";
            layerPrev += "<DOMLayer name=\"Layer " + (index + 1) + "\" color=\"" + backgroundColor + "\" ";
            if (d == 1) {
                layerPrev += " current=\"true\" isSelected=\"true\"";
            }
            if (parentLayer != -1) {
                if (parentLayer != d) {
                    layerPrev += " parentLayerIndex=\"" + (parentLayer) + "\" locked=\"true\"";
                }
            }
            layerPrev += ">";
            String layerAfter = "</DOMLayer>";
            String cf = convertFrames(layerPrev, layerAfter, oneInstanceShapes, tags, timelineTags, characters, d);
            if (cf.equals("")) {
                index--;
            }
            ret += cf;
        }

        int soundLayerIndex = layerCount;
        layerCount++;
        ret += convertSoundLayer(soundLayerIndex, backgroundColor, characters, tags, timelineTags);

        int actionScriptLayerIndex = layerCount;
        layerCount++;
        ret += convertActionScriptLayer(actionScriptLayerIndex, spriteId, tags, timelineTags, backgroundColor);

        ret += "</layers>";
        ret += "</DOMTimeline>";
        return ret;
    }

    private static void writeFile(byte data[], String file) {
        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(data);
        } catch (IOException iex) {
            Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, "Error during file write", iex);
        }
    }

    private static Map<Integer, String> getCharacterClasses(List<Tag> tags) {
        Map<Integer, String> ret = new HashMap<>();
        for (Tag t : tags) {
            if (t instanceof SymbolClassTag) {
                SymbolClassTag sc = (SymbolClassTag) t;
                for (int i = 0; i < sc.tagIDs.length; i++) {
                    if (!ret.containsKey(sc.tagIDs[i]) && !ret.containsValue(sc.classNames[i])) {
                        ret.put(sc.tagIDs[i], sc.classNames[i]);
                    }
                }
            }
        }
        return ret;
    }

    private static Map<Integer, String> getCharacterVariables(List<Tag> tags) {
        Map<Integer, String> ret = new HashMap<>();
        for (Tag t : tags) {
            if (t instanceof ExportAssetsTag) {
                ExportAssetsTag ea = (ExportAssetsTag) t;
                for (int i = 0; i < ea.tags.size(); i++) {
                    if (!ret.containsKey(ea.tags.get(i))) {
                        ret.put(ea.tags.get(i), ea.names.get(i));
                    }
                }
            }
        }
        return ret;
    }

    public static String convertText(List<Tag> tags, TextTag tag, MATRIX matrix, List<FILTER> filters) {
        String ret = "";
        if (matrix == null) {
            matrix = new MATRIX();
        }
        CSMTextSettingsTag csmts = null;
        String filterStr = "";
        if (filters != null) {
            filterStr += "<filters>";
            for (FILTER f : filters) {
                filterStr += convertFilter(f);
            }
            filterStr += "</filters>";
        }

        for (Tag t : tags) {
            if (t instanceof CSMTextSettingsTag) {
                CSMTextSettingsTag c = (CSMTextSettingsTag) t;
                if (c.textID == tag.getCharacterID()) {
                    csmts = c;
                    break;
                }
            }
        }
        String fontRenderingMode = "standard";
        String antiAlias = "";
        if (csmts != null) {
            if (csmts.thickness == 0 & csmts.sharpness == 0) {
                fontRenderingMode = null;
            } else {
                fontRenderingMode = "customThicknessSharpness";
            }
            antiAlias = " antiAliasSharpness=\"" + doubleToString(csmts.sharpness) + "\" antiAliasThickness=\"" + doubleToString(csmts.thickness) + "\"";
        }
        String matStr = "";
        matStr += "<matrix>";
        matStr += convertMatrix(matrix);
        matStr += "</matrix>";
        if ((tag instanceof DefineTextTag) || (tag instanceof DefineText2Tag)) {
            List<TEXTRECORD> textRecords = new ArrayList<>();
            if (tag instanceof DefineTextTag) {
                textRecords = ((DefineTextTag) tag).textRecords;
            } else if (tag instanceof DefineText2Tag) {
                textRecords = ((DefineText2Tag) tag).textRecords;
            }


            looprec:
            for (TEXTRECORD rec : textRecords) {
                if (rec.styleFlagsHasFont) {
                    for (Tag t : tags) {
                        if (t instanceof FontTag) {
                            FontTag ft = (FontTag) t;
                            if (ft.getFontId() == rec.fontId) {
                                if (ft.isSmall()) {
                                    fontRenderingMode = "bitmap";
                                    break looprec;
                                }
                            }
                        }
                    }
                }
            }

            ret += "<DOMStaticText";
            if (fontRenderingMode != null) {
                ret += " fontRenderingMode=\"" + fontRenderingMode + "\"";
            }
            ret += antiAlias;
            ret += " width=\"" + tag.getBounds().getWidth() / 2 + "\" height=\"" + tag.getBounds().getHeight() + "\" autoExpand=\"true\" isSelectable=\"false\">";
            ret += matStr;

            ret += "<textRuns>";
            int fontId = -1;
            FontTag font = null;
            String fontName = null;
            String psFontName = null;
            String availableFonts[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
            int textHeight = -1;
            RGB textColor = null;
            RGBA textColorA = null;
            for (TEXTRECORD rec : textRecords) {
                if (rec.styleFlagsHasColor) {
                    if (tag instanceof DefineTextTag) {
                        textColor = rec.textColor;
                    } else {
                        textColorA = rec.textColorA;
                    }
                }
                if (rec.styleFlagsHasFont) {
                    fontId = rec.fontId;
                    fontName = null;
                    textHeight = rec.textHeight;
                    font = null;
                    for (Tag t : tags) {
                        if (t instanceof FontTag) {
                            if (((FontTag) t).getFontId() == fontId) {
                                font = (FontTag) t;
                            }
                        }
                        if (t instanceof DefineFontNameTag) {
                            if (((DefineFontNameTag) t).fontId == fontId) {
                                fontName = ((DefineFontNameTag) t).fontName;
                            }
                        }
                    }
                    if ((fontName == null) && (font != null)) {
                        fontName = font.getFontName(tags);
                    }
                    psFontName = fontName;
                    if (fontName != null) {
                        for (String avFont : availableFonts) {
                            if (avFont.equals(fontName)) {
                                Font f = new Font(fontName, 0, 10);
                                psFontName = f.getPSName();
                            }
                        }
                    }
                }
                if (font != null) {
                    ret += "<DOMTextRun>";
                    ret += "<characters>" + xmlString(rec.getText(tags, font)) + "</characters>";
                    ret += "<textAttrs>";

                    ret += "<DOMTextAttrs aliasText=\"false\" rotation=\"true\" size=\"" + twipToPixel(textHeight) + "\" bitmapSize=\"1040\"";
                    //indent=\"5\" leftMargin=\"2\" letterSpacing=\"1\" lineSpacing=\"6\" rightMargin=\"3\"
                    if (textColor != null) {
                        ret += " fillColor=\"" + textColor.toHexRGB() + "\"";
                    } else if (textColorA != null) {
                        ret += " fillColor=\"" + textColorA.toHexRGB() + "\" alpha=\"" + textColorA.getAlphaFloat() + "\"";
                    }
                    ret += " face=\"" + psFontName + "\"";
                    ret += "/>";

                    ret += "</textAttrs>";
                    ret += "</DOMTextRun>";
                }
            }
            ret += "</textRuns>";
            ret += filterStr;
            ret += "</DOMStaticText>";
        } else if (tag instanceof DefineEditTextTag) {
            DefineEditTextTag det = (DefineEditTextTag) tag;
            String tagName;
            for (Tag t : tags) {
                if (t instanceof FontTag) {
                    FontTag ft = (FontTag) t;
                    if (ft.getFontId() == det.fontId) {
                        if (ft.isSmall()) {
                            fontRenderingMode = "bitmap";
                            break;
                        }
                    }
                }
            }
            if (!det.useOutlines) {
                fontRenderingMode = "device";
            }
            if (det.wasStatic) {
                tagName = "DOMStaticText";
            } else if (det.readOnly) {
                tagName = "DOMDynamicText";
            } else {
                tagName = "DOMInputText";
            }
            ret += "<" + tagName;
            if (fontRenderingMode != null) {
                ret += " fontRenderingMode=\"" + fontRenderingMode + "\"";
            }
            ret += antiAlias;
            double width = twipToPixel(det.getBounds().getWidth());
            double height = twipToPixel(det.getBounds().getHeight());
            if (det.hasLayout) {
                width -= twipToPixel(det.rightMargin);
                width -= twipToPixel(det.leftMargin);
                //height-=det.
            }
            ret += " width=\"" + width + "\"";
            ret += " height=\"" + height + "\"";
            if (det.border) {
                ret += "  border=\"true\"";
            }
            if (det.html) {
                ret += " renderAsHTML=\"true\"";
            }
            if (det.noSelect) {
                ret += " isSelectable=\"false\"";
            }
            if (det.multiline && det.wordWrap) {
                ret += " lineType=\"multiline\"";
            } else if (det.multiline && (!det.wordWrap)) {
                ret += " lineType=\"multiline no wrap\"";
            } else if (det.password) {
                ret += " lineType=\"password\"";
            }
            if (det.hasMaxLength) {
                ret += " maxCharacters=\"" + det.maxLength + "\"";
            }
            if (!det.variableName.equals("")) {
                ret += " variableName=\"" + det.variableName + "\"";
            }
            ret += ">";
            ret += matStr;
            ret += "<textRuns>";
            if (det.hasText) {
                if (det.html) {
                    ret += convertHTMLText(tags, det, det.initialText);
                } else {
                    ret += "<DOMTextRun>";
                    ret += "<characters>" + xmlString(det.initialText) + "</characters>";
                    int leftMargin = -1;
                    int rightMargin = -1;
                    int indent = -1;
                    int lineSpacing = -1;
                    String alignment = null;
                    boolean italic = false;
                    boolean bold = false;
                    String fontFace = null;
                    int size = -1;
                    if (det.hasFont) {
                        String fontName = null;
                        FontTag ft = null;
                        for (Tag u : tags) {
                            if (u instanceof DefineFontNameTag) {
                                if (((DefineFontNameTag) u).fontId == det.fontId) {
                                    fontName = ((DefineFontNameTag) u).fontName;
                                }
                            }
                            if (u instanceof FontTag) {
                                if (((FontTag) u).getFontId() == det.fontId) {
                                    ft = (FontTag) u;
                                }
                            }
                            if (fontName != null && ft != null) {
                                break;
                            }
                        }
                        if (ft != null) {
                            if (fontName == null) {
                                fontName = ft.getFontName(tags);
                            }
                            italic = ft.isItalic();
                            bold = ft.isBold();
                            size = det.fontHeight;
                            fontFace = new Font(fontName, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName();
                        }
                    }
                    if (det.hasLayout) {
                        leftMargin = det.leftMargin;
                        rightMargin = det.rightMargin;
                        indent = det.indent;
                        lineSpacing = det.leading;
                        String alignNames[] = {"left", "right", "center", "justify"};
                        alignment = alignNames[det.align];
                    }
                    ret += "<textAttrs>";
                    ret += "<DOMTextAttrs";
                    if (alignment != null) {
                        ret += " alignment=\"" + alignment + "\"";
                    }
                    ret += " rotation=\"true\""; //?
                    if (indent > -1) {
                        ret += " indent=\"" + twipToPixel(indent) + "\"";
                    }
                    if (leftMargin > -1) {
                        ret += " leftMargin=\"" + twipToPixel(leftMargin) + "\"";
                    }
                    if (lineSpacing > -1) {
                        ret += " lineSpacing=\"" + twipToPixel(lineSpacing) + "\"";
                    }
                    if (rightMargin > -1) {
                        ret += " rightMargin=\"" + twipToPixel(rightMargin) + "\"";
                    }
                    if (size > -1) {
                        ret += " size=\"" + size + "\"";
                        ret += " bitmapSize=\"" + (size * 20) + "\"";
                    }
                    if (fontFace != null) {
                        ret += " face=\"" + fontFace + "\"";
                    }
                    ret += "/>";
                    ret += "</textAttrs>";
                    ret += "</DOMTextRun>";
                }
            }
            ret += "</textRuns>";
            ret += filterStr;
            ret += "</" + tagName + ">";
        }
        return ret;
    }

    public static void convertSWF(SWF swf, String swfFileName, String outfile, boolean compressed, String generator, String generatorVerName, String generatorVersion, boolean paralel) {
        String domDocument = "";
        String baseName = swfFileName;
        File f = new File(baseName);
        baseName = f.getName();
        if (baseName.contains(".")) {
            baseName = baseName.substring(0, baseName.lastIndexOf("."));
        }
        HashMap<String, byte[]> files = new HashMap<>();
        HashMap<Integer, CharacterTag> characters = getCharacters(swf.tags);
        List<Integer> oneInstaceShapes = getOneInstanceShapes(swf.tags, characters);
        Map<Integer, String> characterClasses = getCharacterClasses(swf.tags);
        Map<Integer, String> characterVariables = getCharacterVariables(swf.tags);

        FileAttributesTag fa = null;
        for (Tag t : swf.tags) {
            if (t instanceof FileAttributesTag) {
                fa = (FileAttributesTag) t;
            }
        }

        boolean useAS3 = false;
        boolean useNetwork = false;
        if (fa != null) {
            useAS3 = fa.actionScript3;
            useNetwork = fa.useNetwork;
        }
        String backgroundColor = "#ffffff";
        for (Tag t : swf.tags) {
            if (t instanceof SetBackgroundColorTag) {
                SetBackgroundColorTag sbc = (SetBackgroundColorTag) t;
                backgroundColor = sbc.backgroundColor.toHexRGB();
            }
        }
        domDocument += "<DOMDocument xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://ns.adobe.com/xfl/2008/\" currentTimeline=\"1\" xflVersion=\"2.2\" creatorInfo=\"" + generator + "\" platform=\"Windows\" versionInfo=\"Saved by " + generatorVerName + "\" majorVersion=\"" + generatorVersion + "\" buildNumber=\"\" nextSceneIdentifier=\"2\" playOptionsPlayLoop=\"false\" playOptionsPlayPages=\"false\" playOptionsPlayFrameActions=\"false\" autoSaveHasPrompted=\"true\"";
        domDocument += " backgroundColor=\"" + backgroundColor + "\"";
        domDocument += " frameRate=\"" + swf.frameRate + "\"";
        domDocument += ">";

        domDocument += convertLibrary(swf, characterVariables, characterClasses, oneInstaceShapes, backgroundColor, swf.tags, characters, files);
        domDocument += "<timelines>";
        domDocument += convertTimeline(0, oneInstaceShapes, backgroundColor, swf.tags, swf.tags, characters, "Scene 1");
        domDocument += "</timelines>";
        domDocument += "</DOMDocument>";
        domDocument = prettyFormatXML(domDocument);


        String publishSettings = "<flash_profiles>\n"
                + "<flash_profile version=\"1.0\" name=\"Default\" current=\"true\">\n"
                + "  <PublishFormatProperties enabled=\"true\">\n"
                + "    <defaultNames>1</defaultNames>\n"
                + "    <flash>1</flash>\n"
                + "    <projectorWin>0</projectorWin>\n"
                + "    <projectorMac>0</projectorMac>\n"
                + "    <html>1</html>\n"
                + "    <gif>0</gif>\n"
                + "    <jpeg>0</jpeg>\n"
                + "    <png>0</png>\n"
                + "    <qt>0</qt>\n"
                + "    <rnwk>0</rnwk>\n"
                + "    <swc>0</swc>\n"
                + "    <flashDefaultName>1</flashDefaultName>\n"
                + "    <projectorWinDefaultName>1</projectorWinDefaultName>\n"
                + "    <projectorMacDefaultName>1</projectorMacDefaultName>\n"
                + "    <htmlDefaultName>1</htmlDefaultName>\n"
                + "    <gifDefaultName>1</gifDefaultName>\n"
                + "    <jpegDefaultName>1</jpegDefaultName>\n"
                + "    <pngDefaultName>1</pngDefaultName>\n"
                + "    <qtDefaultName>1</qtDefaultName>\n"
                + "    <rnwkDefaultName>1</rnwkDefaultName>\n"
                + "    <swcDefaultName>1</swcDefaultName>\n"
                + "    <flashFileName>" + baseName + ".swf</flashFileName>\n"
                + "    <projectorWinFileName>" + baseName + ".exe</projectorWinFileName>\n"
                + "    <projectorMacFileName>" + baseName + ".app</projectorMacFileName>\n"
                + "    <htmlFileName>" + baseName + ".html</htmlFileName>\n"
                + "    <gifFileName>" + baseName + ".gif</gifFileName>\n"
                + "    <jpegFileName>" + baseName + ".jpg</jpegFileName>\n"
                + "    <pngFileName>" + baseName + ".png</pngFileName>\n"
                + "    <qtFileName>" + baseName + ".mov</qtFileName>\n"
                + "    <rnwkFileName>" + baseName + ".smil</rnwkFileName>\n"
                + "    <swcFileName>Untitled-4.swc</swcFileName>\n"
                + "  </PublishFormatProperties>\n"
                + "  <PublishHtmlProperties enabled=\"true\">\n"
                + "    <VersionDetectionIfAvailable>0</VersionDetectionIfAvailable>\n"
                + "    <VersionInfo>12,0,0,0;11,2,0,0;11,1,0,0;10,3,0,0;10,2,153,0;10,1,52,0;9,0,124,0;8,0,24,0;7,0,14,0;6,0,79,0;5,0,58,0;4,0,32,0;3,0,8,0;2,0,1,12;1,0,0,1;</VersionInfo>\n"
                + "    <UsingDefaultContentFilename>1</UsingDefaultContentFilename>\n"
                + "    <UsingDefaultAlternateFilename>1</UsingDefaultAlternateFilename>\n"
                + "    <ContentFilename>" + baseName + "_content.html</ContentFilename>\n"
                + "    <AlternateFilename>" + baseName + "_alternate.html</AlternateFilename>\n"
                + "    <UsingOwnAlternateFile>0</UsingOwnAlternateFile>\n"
                + "    <OwnAlternateFilename></OwnAlternateFilename>\n"
                + "    <Width>" + twipToPixel(swf.displayRect.getWidth()) + "</Width>\n"
                + "    <Height>" + twipToPixel(swf.displayRect.getHeight()) + "</Height>\n"
                + "    <Align>0</Align>\n"
                + "    <Units>0</Units>\n"
                + "    <Loop>1</Loop>\n"
                + "    <StartPaused>0</StartPaused>\n"
                + "    <Scale>0</Scale>\n"
                + "    <HorizontalAlignment>1</HorizontalAlignment>\n"
                + "    <VerticalAlignment>1</VerticalAlignment>\n"
                + "    <Quality>4</Quality>\n"
                + "    <DeblockingFilter>0</DeblockingFilter>\n"
                + "    <WindowMode>0</WindowMode>\n"
                + "    <DisplayMenu>1</DisplayMenu>\n"
                + "    <DeviceFont>0</DeviceFont>\n"
                + "    <TemplateFileName></TemplateFileName>\n"
                + "    <showTagWarnMsg>1</showTagWarnMsg>\n"
                + "  </PublishHtmlProperties>\n"
                + "  <PublishFlashProperties enabled=\"true\">\n"
                + "    <TopDown></TopDown>\n"
                + "    <FireFox></FireFox>\n"
                + "    <Report>0</Report>\n"
                + "    <Protect>0</Protect>\n"
                + "    <OmitTraceActions>0</OmitTraceActions>\n"
                + "    <Quality>80</Quality>\n"
                + "    <DeblockingFilter>0</DeblockingFilter>\n"
                + "    <StreamFormat>0</StreamFormat>\n"
                + "    <StreamCompress>7</StreamCompress>\n"
                + "    <EventFormat>0</EventFormat>\n"
                + "    <EventCompress>7</EventCompress>\n"
                + "    <OverrideSounds>0</OverrideSounds>\n"
                + "    <Version>15</Version>\n"
                + "    <ExternalPlayer>FlashPlayer11.2</ExternalPlayer>\n"
                + "    <ActionScriptVersion>" + (useAS3 ? "3" : "2") + "</ActionScriptVersion>\n"
                + "    <PackageExportFrame>1</PackageExportFrame>\n"
                + "    <PackagePaths></PackagePaths>\n"
                + "    <AS3PackagePaths>.</AS3PackagePaths>\n"
                + "    <AS3ConfigConst>CONFIG::FLASH_AUTHORING=&quot;true&quot;;</AS3ConfigConst>\n"
                + "    <DebuggingPermitted>0</DebuggingPermitted>\n"
                + "    <DebuggingPassword></DebuggingPassword>\n"
                + "    <CompressMovie>" + (swf.compressed ? "1" : "0") + "</CompressMovie>\n"
                + "    <CompressionType>" + (swf.lzma ? "1" : "0") + "</CompressionType>\n"
                + "    <InvisibleLayer>1</InvisibleLayer>\n"
                + "    <DeviceSound>0</DeviceSound>\n"
                + "    <StreamUse8kSampleRate>0</StreamUse8kSampleRate>\n"
                + "    <EventUse8kSampleRate>0</EventUse8kSampleRate>\n"
                + "    <UseNetwork>" + (useNetwork ? 1 : 0) + "</UseNetwork>\n"
                + "    <DocumentClass>" + (characterClasses.containsKey(0) ? characterClasses.get(0) : "") + "</DocumentClass>\n"
                + "    <AS3Strict>2</AS3Strict>\n"
                + "    <AS3Coach>4</AS3Coach>\n"
                + "    <AS3AutoDeclare>4096</AS3AutoDeclare>\n"
                + "    <AS3Dialect>AS3</AS3Dialect>\n"
                + "    <AS3ExportFrame>1</AS3ExportFrame>\n"
                + "    <AS3Optimize>1</AS3Optimize>\n"
                + "    <ExportSwc>0</ExportSwc>\n"
                + "    <ScriptStuckDelay>15</ScriptStuckDelay>\n"
                + "    <IncludeXMP>1</IncludeXMP>\n"
                + "    <HardwareAcceleration>0</HardwareAcceleration>\n"
                + "    <AS3Flags>4102</AS3Flags>\n"
                + "    <DefaultLibraryLinkage>rsl</DefaultLibraryLinkage>\n"
                + "    <RSLPreloaderMethod>wrap</RSLPreloaderMethod>\n"
                + "    <RSLPreloaderSWF>$(AppConfig)/ActionScript 3.0/rsls/loader_animation.swf</RSLPreloaderSWF>\n"
                + "    <LibraryPath>\n"
                + "      <library-path-entry>\n"
                + "        <swc-path>$(AppConfig)/ActionScript 3.0/libs</swc-path>\n"
                + "        <linkage>merge</linkage>\n"
                + "      </library-path-entry>\n"
                + "      <library-path-entry>\n"
                + "        <swc-path>$(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc</swc-path>\n"
                + "        <linkage usesDefault=\"true\">rsl</linkage>\n"
                + "        <rsl-url>http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz</rsl-url>\n"
                + "        <policy-file-url>http://fpdownload.adobe.com/pub/swz/crossdomain.xml</policy-file-url>\n"
                + "        <rsl-url>textLayout_2.0.0.232.swz</rsl-url>\n"
                + "      </library-path-entry>\n"
                + "    </LibraryPath>\n"
                + "    <LibraryVersions>\n"
                + "      <library-version>\n"
                + "        <swc-path>$(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc</swc-path>\n"
                + "        <feature name=\"tlfText\" majorVersion=\"2\" minorVersion=\"0\" build=\"232\"/>\n"
                + "        <rsl-url>http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz</rsl-url>\n"
                + "        <policy-file-url>http://fpdownload.adobe.com/pub/swz/crossdomain.xml</policy-file-url>\n"
                + "        <rsl-url>textLayout_2.0.0.232.swz</rsl-url>\n"
                + "      </library-version>\n"
                + "    </LibraryVersions>\n"
                + "  </PublishFlashProperties>\n"
                + "  <PublishJpegProperties enabled=\"true\">\n"
                + "    <Width>" + twipToPixel(swf.displayRect.getWidth()) + "</Width>\n"
                + "    <Height>" + twipToPixel(swf.displayRect.getHeight()) + "</Height>\n"
                + "    <Progressive>0</Progressive>\n"
                + "    <DPI>4718592</DPI>\n"
                + "    <Size>0</Size>\n"
                + "    <Quality>80</Quality>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "  </PublishJpegProperties>\n"
                + "  <PublishRNWKProperties enabled=\"true\">\n"
                + "    <exportFlash>1</exportFlash>\n"
                + "    <flashBitRate>0</flashBitRate>\n"
                + "    <exportAudio>1</exportAudio>\n"
                + "    <audioFormat>0</audioFormat>\n"
                + "    <singleRateAudio>0</singleRateAudio>\n"
                + "    <realVideoRate>100000</realVideoRate>\n"
                + "    <speed28K>1</speed28K>\n"
                + "    <speed56K>1</speed56K>\n"
                + "    <speedSingleISDN>0</speedSingleISDN>\n"
                + "    <speedDualISDN>0</speedDualISDN>\n"
                + "    <speedCorporateLAN>0</speedCorporateLAN>\n"
                + "    <speed256K>0</speed256K>\n"
                + "    <speed384K>0</speed384K>\n"
                + "    <speed512K>0</speed512K>\n"
                + "    <exportSMIL>1</exportSMIL>\n"
                + "  </PublishRNWKProperties>\n"
                + "  <PublishGifProperties enabled=\"true\">\n"
                + "    <Width>" + twipToPixel(swf.displayRect.getWidth()) + "</Width>\n"
                + "    <Height>" + twipToPixel(swf.displayRect.getHeight()) + "</Height>\n"
                + "    <Animated>0</Animated>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "    <Loop>1</Loop>\n"
                + "    <LoopCount></LoopCount>\n"
                + "    <OptimizeColors>1</OptimizeColors>\n"
                + "    <Interlace>0</Interlace>\n"
                + "    <Smooth>1</Smooth>\n"
                + "    <DitherSolids>0</DitherSolids>\n"
                + "    <RemoveGradients>0</RemoveGradients>\n"
                + "    <TransparentOption></TransparentOption>\n"
                + "    <TransparentAlpha>128</TransparentAlpha>\n"
                + "    <DitherOption></DitherOption>\n"
                + "    <PaletteOption></PaletteOption>\n"
                + "    <MaxColors>255</MaxColors>\n"
                + "    <PaletteName></PaletteName>\n"
                + "  </PublishGifProperties>\n"
                + "  <PublishPNGProperties enabled=\"true\">\n"
                + "    <Width>" + twipToPixel(swf.displayRect.getWidth()) + "</Width>\n"
                + "    <Height>" + twipToPixel(swf.displayRect.getHeight()) + "</Height>\n"
                + "    <OptimizeColors>1</OptimizeColors>\n"
                + "    <Interlace>0</Interlace>\n"
                + "    <Transparent>0</Transparent>\n"
                + "    <Smooth>1</Smooth>\n"
                + "    <DitherSolids>0</DitherSolids>\n"
                + "    <RemoveGradients>0</RemoveGradients>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "    <DitherOption></DitherOption>\n"
                + "    <FilterOption></FilterOption>\n"
                + "    <PaletteOption></PaletteOption>\n"
                + "    <BitDepth>24-bit with Alpha</BitDepth>\n"
                + "    <MaxColors>255</MaxColors>\n"
                + "    <PaletteName></PaletteName>\n"
                + "  </PublishPNGProperties>\n"
                + "  <PublishQTProperties enabled=\"true\">\n"
                + "    <Width>" + twipToPixel(swf.displayRect.getWidth()) + "</Width>\n"
                + "    <Height>" + twipToPixel(swf.displayRect.getHeight()) + "</Height>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "    <UseQTSoundCompression>0</UseQTSoundCompression>\n"
                + "    <AlphaOption></AlphaOption>\n"
                + "    <LayerOption></LayerOption>\n"
                + "    <QTSndSettings>00000000</QTSndSettings>\n"
                + "    <ControllerOption>0</ControllerOption>\n"
                + "    <Looping>0</Looping>\n"
                + "    <PausedAtStart>0</PausedAtStart>\n"
                + "    <PlayEveryFrame>0</PlayEveryFrame>\n"
                + "    <Flatten>1</Flatten>\n"
                + "  </PublishQTProperties>\n"
                + "</flash_profile>\n"
                + "</flash_profiles>";

        if (compressed) {
            try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outfile))) {
                out.putNextEntry(new ZipEntry("DOMDocument.xml"));
                out.write(domDocument.getBytes("UTF-8"));
                out.putNextEntry(new ZipEntry("PublishSettings.xml"));
                out.write(publishSettings.getBytes("UTF-8"));
                for (String fileName : files.keySet()) {
                    out.putNextEntry(new ZipEntry("LIBRARY/" + fileName));
                    out.write(files.get(fileName));
                }
            } catch (IOException ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
            }

        } else {
            File xfl = new File(outfile);
            File outDir = xfl.getParentFile();
            outDir.mkdirs();
            try {
                writeFile(domDocument.getBytes("UTF-8"), outDir.getAbsolutePath() + File.separator + "DOMDocument.xml");
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                writeFile(publishSettings.getBytes("UTF-8"), outDir.getAbsolutePath() + File.separator + "PublishSettings.xml");
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
            }
            File libraryDir = new File(outDir.getAbsolutePath() + File.separator + "LIBRARY");
            libraryDir.mkdir();
            for (String fileName : files.keySet()) {
                writeFile(files.get(fileName), libraryDir.getAbsolutePath() + File.separator + fileName);
            }

            writeFile("PROXY-CS5".getBytes(), outfile);
        }
        if (useAS3) {
            File outF = new File(outfile);
            File outDir = outF.getParentFile();
            try {
                swf.exportActionScript(outDir.getAbsolutePath(), false, paralel);
            } catch (Exception ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, "Error during ActionScript3 export", ex);
            }
        }
    }

    private static int normHue(double h) {
        if (Double.isNaN(h)) {
            h = -Math.PI;
        }
        int ret = (int) Math.round(h * 180 / Math.PI);
        while (ret > 180) {
            ret -= 360;
        }
        while (ret < -180) {
            ret += 360;
        }
        return ret;
    }

    private static int normBrightness(double b) {
        if (Double.isNaN(b)) {
            b = -100;
        }
        return (int) Math.round(b);
    }

    private static int normSaturation(double s) {
        if (Double.isNaN(s)) {
            return -100;
        } else if (s == 1) {
            return 0;
        } else if (s - 1 < 0) {
            return (int) Math.round((s - 1) * 100);
        } else {
            return (int) Math.round(((s - 1) * 100) / 3);
        }
    }

    private static int normContrast(double c) {
        double ctrMap[] = {
            //      0     1     2     3     4     5     6     7     8     9
            /*0*/0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11,
            /*1*/ 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
            /*2*/ 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
            /*3*/ 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68,
            /*4*/ 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
            /*5*/ 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
            /*6*/ 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25,
            /*7*/ 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8,
            /*8*/ 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0,
            /*9*/ 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8,
            /*10*/ 10.0};
        if (c == 127) {
            return 0;
        } else if (c - 127 < 0) {
            return (int) Math.round((c - 127) * 100.0 / 127.0);
        } else {
            c = (c - 127) / 127;
            for (int i = 0; i < ctrMap.length; i++) {
                if (ctrMap[i] >= c) {
                    return i;
                }
            }
        }
        return ctrMap.length - 1;
    }

    private static boolean sameDouble(double a, double b) {
        final double EPSILON = 0.00001;
        return a == b ? true : Math.abs(a - b) < EPSILON;
    }

    public static String convertAdjustColorFilter(COLORMATRIXFILTER filter) {
        float matrix[][] = new float[5][5];
        int index = 0;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 5; j++) {
                matrix[j][i] = filter.matrix[index];
                index++;
            }
        }
        double a11 = matrix[0][0], a12 = matrix[0][1], a13 = matrix[0][2],
                a21 = matrix[1][0], a22 = matrix[1][1], a23 = matrix[1][2],
                a31 = matrix[2][0], a32 = matrix[2][1], a33 = matrix[2][2],
                a41 = matrix[4][0];

        double b, c, h, s;
        b = (24872168661075.0 * a11 * a11 - 151430415740925.0 * a12 + 341095051289483.0 * a12 * a12 - 15302094789450.0 * a13 + 82428663495404.0 * a12 * a13
                - 4592294873812.0 * a13 * a13 + 43556251470.0 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13
                - 930 * a11 * (287 * a12 + 178 * a13)) + 2384730956550.0 * a12 * a41 + 240977870700.0 * a13 * a41
                - 685925220 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13))
                * a41 + 465 * a11 * (466201717582.0 * a12 + 55756962908.0 * a13 + 764132175 * (-127 + 2 * a41)))
                / (391687695450.0 * a11 * a11 + 5371575610858.0 * a12 * a12 + 1298089188904.0 * a12 * a13 - 72319604312.0 * a13 * a13
                + 1860 * a11 * (1835439833 * a12 + 219515602 * a13));
        c = (127 * (495225 * a11 + 1661845 * a12 + 167930 * a13
                + 478 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13))))
                / 717495;
        h = 2 * (Math.atan((-465 * a11 + 287 * a12 + 178 * a13 + Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13
                - 930 * a11 * (287 * a12 + 178 * a13))) / (500. * (a12 - a13))) + Math.PI/*+ Pi*C(1)*/);
        s = (1543 * (-103355550 * a11 * a11 - 158872382 * a12 * a12 + 190161784 * a12 * a13 - 134644952 * a13 * a13
                + 1661845 * a12 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13
                - 930 * a11 * (287 * a12 + 178 * a13)) + 167930 * a13
                * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13))
                + 465 * a11 * (274372 * a12 + 170168 * a13 + 1065 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13
                + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13)))))
                / (195843847725.0 * a11 * a11 + 2685787805429.0 * a12 * a12 + 649044594452.0 * a12 * a13 - 36159802156.0 * a13 * a13
                + 930 * a11 * (1835439833 * a12 + 219515602 * a13));

        if (sameDouble(410 * a12, 1543 * a31) && sameDouble(410 * a12, 1543 * a32) && sameDouble(3047 * a12, 1543 * a21) && sameDouble(3047 * a12, 1543 * a23)
                && sameDouble(a22, a11 + (1504 * a12) / 1543.) && sameDouble((1133 * a12) / 1543. + a33, a11)
                /*&& (b == (195961 * a11 + 439039 * a12 + 1543 * (-127 + 2 * a41)) / (3086 * a11 + 6914 * a12))
                 && (c == 127 * a11 + (439039 * a12) / 1543.) && (s == (1543 * (a11 - a12)) / (1543 * a11 + 3457 * a12))
                 */ && !sameDouble(a11, a12) && !sameDouble(1543 * a11 + 3457 * a12, 0)) {
            h = 0;
        }

        return "<AdjustColorFilter brightness=\"" + normBrightness(b) + "\" contrast=\"" + normContrast(c) + "\" saturation=\"" + normSaturation(s) + "\" hue=\"" + normHue(h) + "\"/>";
    }

    private static String convertHTMLText(List<Tag> tags, DefineEditTextTag det, String html) {
        HTMLTextParser tparser = new HTMLTextParser(tags, det);
        XMLReader parser;
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //factory.setValidating(false);
            parser = XMLReaderFactory.createXMLReader();
            parser.setContentHandler(tparser);
            parser.setErrorHandler(tparser);
            //parser.setFeature("http://xml.org/sax/features/validation", false);
            html = "<?xml version=\"1.0\"?>\n"
                    + "<!DOCTYPE some_name [ \n"
                    + "<!ENTITY nbsp \"&#160;\"> \n"
                    + "]><html>" + html + "</html>";
            try {
                parser.parse(new InputSource(new InputStreamReader(new ByteArrayInputStream(html.getBytes("UTF-8")), "UTF-8")));
            } catch (SAXParseException spe) {
                System.out.println(html);
                System.err.println(tparser.result);
            }
            //XMLReader reader = parser.getXMLReader();


            /*reader.setContentHandler(tparser);
             reader.setErrorHandler(tparser);
             reader.setEntityResolver(tparser);
             html = "<html>" + html + "</html>";
             reader.parse(new InputSource(new InputStreamReader(new ByteArrayInputStream(html.getBytes("UTF-8")), "UTF-8")));*/

        } catch (Exception e) {
            Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, "Error while converting HTML", e);
        }
        return tparser.result;
    }

    private static String xmlString(String s) {
        return s.replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;").replace("&", "&amp;").replace("\r\n", "&#xD;").replace("\r", "&#xD;").replace("\n", "&#xD;");
    }

    private static double twipToPixel(double tw) {
        return tw / 20.0;
    }

    private static class HTMLTextParser extends DefaultHandler {

        public String result = "";
        private String fontFace = "";
        private String color = "";
        private int size = -1;
        private int indent = -1;
        private int leftMargin = -1;
        private int rightMargin = -1;
        private int lineSpacing = -1;
        private double letterSpacing = -1;
        private String alignment = null;
        private List<Tag> tags;
        private boolean bold = false;
        private boolean italic = false;
        private boolean underline = false;
        private boolean li = false;
        private String url = null;
        private String target = null;

        @Override
        public void error(SAXParseException e) throws SAXException {
        }

        @Override
        public void fatalError(SAXParseException e) throws SAXException {
        }

        @Override
        public void warning(SAXParseException e) throws SAXException {
        }

        public HTMLTextParser(List<Tag> tags, DefineEditTextTag det) {
            if (det.hasFont) {
                String fontName = null;
                FontTag ft = null;
                for (Tag u : tags) {
                    if (u instanceof DefineFontNameTag) {
                        if (((DefineFontNameTag) u).fontId == det.fontId) {
                            fontName = ((DefineFontNameTag) u).fontName;
                        }
                    }
                    if (u instanceof FontTag) {
                        if (((FontTag) u).getFontId() == det.fontId) {
                            ft = (FontTag) u;
                        }
                    }
                    if (fontName != null && ft != null) {
                        break;
                    }
                }
                if (ft != null) {
                    if (fontName == null) {
                        fontName = ft.getFontName(tags);
                    }
                    italic = ft.isItalic();
                    bold = ft.isBold();
                    size = det.fontHeight;
                    fontFace = new Font(fontName, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName();
                }
            }
            if (det.hasLayout) {
                leftMargin = det.leftMargin;
                rightMargin = det.rightMargin;
                indent = det.indent;
                lineSpacing = det.leading;
                String alignNames[] = {"left", "right", "center", "justify"};
                alignment = alignNames[det.align];
            }
            this.tags = tags;
        }

        @Override
        public void startDocument() throws SAXException {
        }

        @Override
        public void startElement(String uri, String localName,
                String qName, Attributes attributes) throws SAXException {
            if (qName.equals("a")) {
                String href = attributes.getValue("href");
                if (href != null) {
                    url = href;
                }
                String t = attributes.getValue("target");
                if (t != null) {
                    target = t;
                }
            } else if (qName.equals("b")) {
                bold = true;
            } else if (qName.equals("i")) {
                italic = true;
            } else if (qName.equals("u")) {
                underline = true;
            } else if (qName.equals("li")) {
                li = true;
            } else if (qName.equals("p")) {
                String a = attributes.getValue("align");
                if (a != null) {
                    alignment = a;
                }
                if (!result.equals("")) {
                    putText("\r\n");
                }
            } else if (qName.equals("font")) {
                //kerning  ?
                String ls = attributes.getValue("letterSpacing");
                if (ls != null) {
                    letterSpacing = Double.parseDouble(ls);
                }
                String s = attributes.getValue("size");
                if (s != null) {
                    size = Integer.parseInt(s);
                }
                String c = attributes.getValue("color");
                if (c != null) {
                    color = c;
                }
                String f = attributes.getValue("face");
                if (f != null) {
                    for (Tag t : tags) {
                        if (t instanceof FontTag) {
                            FontTag ft = (FontTag) t;
                            String fontName = "";
                            if (f.equals(ft.getFontName(tags))) {
                                for (Tag u : tags) {
                                    if (u instanceof DefineFontNameTag) {
                                        if (((DefineFontNameTag) u).fontId == ft.getFontId()) {
                                            fontName = ((DefineFontNameTag) u).fontName;
                                        }
                                    }
                                }
                                if (fontName == null) {
                                    fontName = ft.getFontName(tags);
                                }
                                fontFace = new Font(fontName, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName();
                                break;
                            }
                        }
                    }
                }
            }
            //textformat,tab,br
        }

        @Override
        public void endElement(String uri, String localName,
                String qName) throws SAXException {
            if (qName.equals("a")) {
                url = null;
                target = null;
            }
            if (qName.equals("b")) {
                bold = false;
            }
            if (qName.equals("i")) {
                italic = false;
            }
            if (qName.equals("u")) {
                underline = false;
            }
            if (qName.equals("li")) {
                li = false;
            }
        }

        private void putText(String txt) {

            result += "<DOMTextRun>";
            result += "<characters>" + xmlString(txt) + "</characters>";
            result += "<textAttrs>";
            result += "<DOMTextAttrs";
            if (alignment != null) {
                result += " alignment=\"" + alignment + "\"";
            }
            result += " rotation=\"true\""; //?
            if (indent > -1) {
                result += " indent=\"" + twipToPixel(indent) + "\"";
            }
            if (leftMargin > -1) {
                result += " leftMargin=\"" + twipToPixel(leftMargin) + "\"";
            }
            if (letterSpacing > -1) {
                result += " letterSpacing=\"" + letterSpacing + "\"";
            }
            if (lineSpacing > -1) {
                result += " lineSpacing=\"" + twipToPixel(lineSpacing) + "\"";
            }
            if (rightMargin > -1) {
                result += " rightMargin=\"" + twipToPixel(rightMargin) + "\"";
            }
            if (size > -1) {
                result += " size=\"" + size + "\"";
                result += " bitmapSize=\"" + (size * 20) + "\"";
            }
            if (fontFace != null) {
                result += " face=\"" + fontFace + "\"";
            }
            if (color != null) {
                result += " fillColor=\"" + color + "\"";
            }
            if (url != null) {
                result += " url=\"" + url + "\"";
            }
            if (target != null) {
                result += " target=\"" + target + "\"";
            }
            result += "/>";
            result += "</textAttrs>";
            result += "</DOMTextRun>";
        }

        @Override
        public void characters(char[] ch, int start, int length)
                throws SAXException {
            putText(new String(ch, start, length));

        }

        @Override
        public void endDocument() {
        }
    }
}
